TypeScript Notes

 🛣️ TypeScript Roadmap (With Topics)

1. TypeScript Basics (Core Concepts)

  • What is TypeScript? Why use it over JavaScript?

  • Installing and setting up TypeScript (tsc, tsconfig.json)

  • Type Annotations

    • string, number, boolean, any, unknown, void, never, null, undefined

  • Type Inference

  • Union (|) and Intersection (&) Types

  • Literal Types

  • Enums

  • Type Aliases vs Interfaces

  • Tuples

  • Type Assertions (as keyword)

  • const, let, var differences (TS-specific cases)

  • Type narrowing (typeof, in, instanceof)


📦 2. Functions and Objects

  • Function Types (with params & return types)

  • Optional and Default Parameters

  • Rest Parameters & Overloads

  • Function type alias

  • Callbacks with type

  • Object Types & Index Signatures

  • Readonly Properties

  • Destructuring with types


📚 3. Advanced Types

  • Interfaces vs Type Aliases (deep dive)

  • Type Extensions (extends)

  • keyof, typeof, in, infer, as

  • Mapped Types

  • Conditional Types

  • Utility Types (Partial, Required, Pick, Omit, Record, Exclude, Extract, NonNullable)

  • Discriminated Unions

  • Template Literal Types

  • Recursive Types


🧱 4. Classes and OOP in TypeScript

  • Public, Private, Protected, and Readonly modifiers

  • Accessors (get / set)

  • Abstract Classes

  • Implements (Interface in class)

  • Static properties and methods

  • Generics with classes and functions

  • Generic Constraints


🧰 5. Generics (Important for Interviews!)

  • Basics: <T>

  • Generic Constraints (extends)

  • Generic Functions

  • Generic Interfaces and Classes

  • keyof T, T[K]

  • Advanced: Nested Generics, Conditional Types with Generics


🛠️ 6. Working with Modules & Namespaces

  • ES Module syntax in TS

  • import, export, default

  • Type-only imports (import type)

  • Declaration Merging

  • Type Declaration Files (.d.ts)

  • Ambient Declarations (declare)


🌐 7. TypeScript with Frontend Frameworks

  • TypeScript with React (TSX): Props, State, Refs, Context API, Custom Hooks

  • TypeScript with Next.js

  • TypeScript with Node.js / Express

  • Type-safe validation with Zod

  • TypeScript with API calls (using Axios or Fetch)


🧠 8. TypeScript in Real-world Apps

  • Typing Redux or Zustand state

  • Handling APIs (typing response/request)

  • Handling form data with TypeScript

  • Error handling (try/catch) with proper type guards

  • Project structure and best practices


💼 9. Interview-Focused Topics

  • Deep understanding of type system (e.g. never, unknown, infer)

  • Advanced Generics usage

  • Utility Types with real-world examples

  • Differences between interface vs type

  • When to use which type system feature

  • Refactoring JavaScript to TypeScript

  • Performance benefits and compilation process

  • Real-world scenarios (System Design with TS)










------------------------------------------------------------------------------------------------------------

✅ 1. TypeScript Basics (Core Concepts)




1. What is TypeScript? Why use it over JavaScript?

Ans.

✅ 1. Formal Definition with Example

TypeScript is a statically typed superset of JavaScript developed by Microsoft.
It adds optional static types to JavaScript, allowing developers to catch errors at compile time rather than at runtime. TypeScript code is compiled (or "transpiled") into plain JavaScript so it can run in any browser or JavaScript environment.

Think of it as JavaScript + types + better tooling support.

📌 Example:

let age: number = 25; // Static type: number age = "twenty-five"; // ❌ Error: Type 'string' is not assignable to type 'number'

In JavaScript, this would NOT throw an error until runtime. TypeScript gives you an error during development.


💻 2. Code Example + Explanation

function greetUser(name: string): string { return `Hello, ${name}`; } const result = greetUser("Ayan"); console.log(result); // Output: Hello, Ayan

✅ Code Breakdown:

  • name: string: This tells TS that the name parameter must be a string.

  • : string after the function means the return value will be a string.

  • If you try greetUser(123), TS will throw an error before running the code.

  • This helps catch bugs earlier and improves code safety and predictability.


😎 3. Easy/Slang Explanation

Yo, imagine JavaScript is like a chill friend who lets you do whatever you want — even dumb stuff like saying your age is "banana". 😂
But TypeScript? That’s your strict but helpful buddy who stops you and says,

“Bro, you said age is a number, why are you feeding it a string? Fix it.”

So basically:

  • JS is flexible but risky

  • TS is strict but safe

  • It’s like putting guardrails on your JavaScript so you don’t crash your code later 🚧

TypeScript catches bugs before you even run the app, helps VSCode give you better auto-complete, and makes big apps way easier to manage.






2. Installing and setting up TypeScript (tsc, tsconfig.json)?

Ans.

✅ 1. Formal Definition with Example

Installing TypeScript means getting the TypeScript compiler (tsc) onto your system, which translates .ts files into regular JavaScript.

Setting up TypeScript involves creating a tsconfig.json file, which tells the compiler how to behave — like what files to compile, what version of JS to target, and which strict rules to apply.


📦 Installation (Globally):

npm install -g typescript
  • Installs tsc (TypeScript Compiler) globally so you can use it anywhere via CLI.

📁 Create a project and initialize it:

mkdir my-ts-app cd my-ts-app npm init -y npm install typescript --save-dev npx tsc --init

This creates a tsconfig.json file.

🧾 Sample tsconfig.json:

{ "compilerOptions": { "target": "ES6", "module": "commonjs", "strict": true, "outDir": "./dist", "rootDir": "./src" }, "include": ["src"] }

💻 2. Code Example + Explanation

Folder Structure:

my-ts-app/ ├── src/ │ └── index.ts ├── dist/ ├── tsconfig.json ├── package.json

src/index.ts:

const greet = (name: string): string => { return `Hello, ${name}`; } console.log(greet("Ayan"));

Compile the project:

npx tsc

------------------------

It will generate the compiled JavaScript in dist/index.js:

"use strict"; const greet = (name) => { return `Hello, ${name}`; }; console.log(greet("Ayan"));

Explanation:

  • npx tsc reads from tsconfig.json and compiles .ts files to .js.

  • strict: true enables strict type checking.

  • outDir: compiled JS goes here (dist/)

  • rootDir: your TS source code is here (src/)


😎 3. Easy/Slang Explanation

Alright, here's the vibe:

You can't just write TypeScript and expect the browser to understand it — it speaks JavaScript only. So you need a translator, and that’s tsc (TypeScript Compiler). You install it with npm.

Then you tell it:

“Yo compiler, here’s how I want you to behave”
...and you write all those instructions inside tsconfig.json — like what version of JS to output, where to dump the compiled code, etc.

So basically:

  • Install tsc = Getting the translator.

  • Write .ts code = Speak TypeScript.

  • Run tsc = It turns that TypeScript into normal JavaScript.

  • tsconfig.json = The rulebook for how the translation should happen.

Once you set this up, you’re ready to start writing real TypeScript apps. 🔥






3. Type Annotations: string, number, boolean, any, unknown, void, never, null, undefined?

Ans.

✅ 1. Formal Definition with Example

Type annotations in TypeScript allow you to explicitly declare the type of a variable, function parameter, or return value.
This helps catch bugs during development and provides better tooling and autocomplete.

Syntax: let variableName: type = value;


🔤 Common Primitive Type Annotations:

TypeDescriptionExample
stringText valueslet name: string = "Ayan"
numberNumeric values (int, float, etc.)let age: number = 22
booleantrue or false valueslet isAdmin: boolean = false
anyTurns off type checking (accepts any type)let data: any = "hello"
unknownLike any, but safer — must be checked before usinglet userInput: unknown = 5
voidFunctions that return nothingfunction log(): void {}
neverFunctions that never return (e.g. throw errors)function fail(): never {}
nullA null valuelet x: null = null
undefinedA variable that hasn’t been assignedlet y: undefined = undefined

💻 2. Code Example + Explanation

let username: string = "Ayan"; let age: number = 23; let isLoggedIn: boolean = true; let anything: any = "hello"; let maybeInput: unknown = 42; function logMessage(msg: string): void { console.log(msg); } function throwError(): never { throw new Error("Something went wrong!"); } let nothingHere: null = null; let notAssigned: undefined = undefined;

🧠 Explanation:

  • username is restricted to strings. You can't assign a number to it.

  • anything can hold any value — disables type safety (not recommended).

  • maybeInput is unknown — TypeScript forces you to check its type before using it.

  • logMessage() returns nothing → type is void.

  • throwError() never finishes — it throws an error → type is never.

  • null and undefined are their own types in TS when strict mode is enabled.


😎 3. Easy/Slang Explanation

Alright, here’s the chill version:

Type annotations are like labels you slap on variables so TypeScript can babysit your code.

Think of it like this:

  • string: for anything in quotes like "hello", "Ayan"

  • number: for numbers, obviously — 69, 420.5, etc.

  • boolean: true/false stuff. Like "Is pizza awesome?"true

  • any: wildcard — like the "whatever dude" type 😅 (but can be dangerous)

  • unknown: mystery box. You gotta check it before using.

  • void: when your function does stuff but doesn’t return anything.

  • never: when your function just throws hands and never comes back (like throw)

  • null: literally nothing.

  • undefined: "I exist, but nobody gave me a value."


TS lets you write:

let mood: string = "chill";


Now if you try:

mood = 404; // ❌ TS be like: "Nah fam, that ain't a string."

This way, TypeScript watches your back and stops you from doing dumb stuff before you even run the code.





4. Type Inference?

Ans.

✅ 1. Formal Definition with Example

Type Inference in TypeScript is the ability of the compiler to automatically guess the type of a variable or expression based on the assigned value, even if you don’t explicitly declare it.

In other words, if you don’t write the type, TypeScript will try to figure it out based on context.


🧾 Example:

let username = "Ayan"; // inferred as string let age = 25; // inferred as number

Here, even though you didn’t write : string or : number, TypeScript already knows what those types are — because of the values you assigned.


💻 2. Code Example + Explanation

let isLoggedIn = true; // Inferred as boolean function multiply(x: number, y: number) { return x * y; // Inferred return type: number } const result = multiply(4, 5); // result is inferred as number

🔍 Code Breakdown:

  • isLoggedIn is inferred as a boolean, because you gave it true.

  • multiply() parameters are explicitly typed, but the return type is inferred as number based on the operation x * y.

  • result is inferred as number because it holds the return value of multiply.

✅ TypeScript helps you out even if you don’t always annotate everything.


😎 3. Easy/Slang Explanation

Alright, here’s the lazy dev’s dream:
TypeScript looks at what you write and goes,

“Oh okay, I see you… I know what type that is. No need to spell it out.”

You write:

let name = "Ayan";

TS goes: “Hmm… that’s a string, got it.” ✅
You don’t have to write : string.

BUT — if you mess it up later:

name = 123; // ❌ TypeScript: “Nah bro, that's a string. Don’t try funny stuff.”

So basically:

  • You give it a clue (initial value), and TS figures out the rest.

  • Works with vars, return values, arrays, objects — even functions!

  • Keeps your code clean but still type-safe.

It’s like TS is smart enough to say:

“I saw what you did there — I’ll keep an eye on it.” 👀


Type inference makes TypeScript feel like JavaScript while still giving you the safety net of types.






5. Union (|) and Intersection (&) Types?

Ans.

✅ 1. Formal Definition with Example

🔹 Union Types (|)

A Union type means a variable can hold one of multiple types.
It’s written with the | (pipe) operator.

It’s like saying: “This can be a this OR a that.”

let value: string | number; value = "Ayan"; // ✅ valid value = 99; // ✅ valid value = true; // ❌ Error: not string or number

🔸 Intersection Types (&)

An Intersection type combines multiple types into one.
It’s written with the & (ampersand) operator.

It’s like saying: “This has to be this AND that at the same time.”

type User = { name: string }; type Admin = { role: string }; type AdminUser = User & Admin; const admin: AdminUser = { name: "Ayan", role: "superadmin" };

💻 2. Code Example + Explanation

🔹 Union Type Example:

function printId(id: number | string) { console.log("Your ID is:", id); } printId(101); // ✅ number printId("XYZ101"); // ✅ string printId(true); // ❌ Error

🧠 Explanation:

  • The id parameter accepts either a number OR a string.

  • TS will prevent any other types from being passed.


🔸 Intersection Type Example:

type Person = { name: string; age: number; }; type Developer = { skills: string[]; }; type DevPerson = Person & Developer; const dev: DevPerson = { name: "Ayan", age: 23, skills: ["TypeScript", "React"] };

🧠 Explanation:

  • DevPerson combines both Person and Developer.

  • The object dev must have all the fields from both types.


😎 3. Easy/Slang Explanation

Okayyy, real talk:

🧃 Union (|): “Pick One”

Imagine you’re ordering a drink:

let drink: "Tea" | "Coffee";

You're saying: “Bro, I’ll take either Tea or Coffee, but not both.”

So:

drink = "Tea"; // ✅ chill drink = "Coffee"; // ✅ also chill drink = "Milk"; // ❌ TypeScript be like: “Who ordered this?”

🧩 Intersection (&): “All Together Now”

Now imagine you’re building a character that’s both a wizard and a warrior:

type Wizard = { mana: number }; type Warrior = { strength: number }; type Battlemage = Wizard & Warrior;

Your Battlemage gotta have both mana AND strength.

So:

const b: Battlemage = { mana: 50, strength: 80 }; // ✅ Magic AND muscles

If you leave out one, TypeScript says:

“Ayo, you're missing something!”


🔥 Summary:

ConceptSymbolMeaningThink of it as...
Union Type`  `    One of the types (OR)
Intersection&All types combined (AND)    “Include everything”






6. Literal Types?

Ans.

✅ 1. Formal Definition with Example

Literal Types in TypeScript allow you to specify exact values a variable can have, rather than just a general type.

It's like saying: "This variable can only be this value or a few specific values — nothing else."

You can create literal types for:

  • Strings → "success", "error"

  • Numbers → 0, 1

  • Booleans → true, false


📌 Example:

let status: "success" | "error" | "loading"; status = "success"; // ✅ valid status = "error"; // ✅ valid status = "failed"; // ❌ Error: Not one of the allowed values

💻 2. Code Example + Explanation

💬 Literal type in action:

type Direction = "left" | "right" | "up" | "down"; function move(direction: Direction) { console.log(`Moving ${direction}`); } move("left"); // ✅ OK move("up"); // ✅ OK move("back"); // ❌ Error: "back" is not a valid direction

🧠 Code Breakdown:

  • Direction is a custom type that can only be "left", "right", "up", or "down".

  • You can’t pass any other string — TS will block it.

  • This is great for fixed options (like enums) or API status codes.


😎 3. Easy/Slang Explanation

Alright, here’s the real-talk version:

Literal types are like that one friend who's super picky:

"Bro, I’m only eating pizza, burgers, or tacos. Don’t bring me anything else."

So instead of letting a variable be any string, you say:

let food: "pizza" | "burger" | "taco";

Now:

food = "pizza"; // ✅ Cool food = "sushi"; // ❌ Nah bro, that’s not on the list.

It’s TypeScript being strict like:

“Pick only from this exact list — no surprises.”

Literal types are 🔥 for:

  • Defining allowed button styles: "primary" | "secondary"

  • HTTP methods: "GET" | "POST" | "PUT" | "DELETE"

  • Mode switches: "dark" | "light"

  • Status handling: "loading" | "success" | "error"


🧠 Pro Tip:

You can also use literals with as const:

const BUTTON_TYPE = { PRIMARY: "primary", SECONDARY: "secondary" } as const; type ButtonType = typeof BUTTON_TYPE[keyof typeof BUTTON_TYPE];

That locks in the values and gives you literal type power from objects. 💪





7. Enums?

Ans.

✅ 1. Formal Definition with Example

Enums (short for "Enumerations") are a special TypeScript feature that lets you define a group of named constant values.
They make your code more readable and manageable when you're working with a fixed set of options.

There are two main types:

  • Numeric Enums (default)

  • String Enums


📌 Example:

enum Status { Success, Error, Loading }
  • By default, Success = 0, Error = 1, Loading = 2

You can use it like:

const currentStatus: Status = Status.Success;

💻 2. Code Example + Explanation

🔢 Numeric Enum:

enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 } function move(dir: Direction) { if (dir === Direction.Left) { console.log("Moving Left"); } } move(Direction.Left); // Output: Moving Left

🔤 String Enum:

enum UserRole { Admin = "ADMIN", Editor = "EDITOR", Guest = "GUEST" } function access(role: UserRole) { console.log("Access level:", role); } access(UserRole.Admin); // Output: Access level: ADMIN

🧠 Explanation:

  • Enums replace magic numbers or strings with descriptive labels.

  • Direction.Left makes more sense than just 2 or "LEFT".

  • TypeScript ensures you can only use defined enum values.

  • Numeric enums auto-increment from 0, unless you assign a custom number.


😎 3. Easy/Slang Explanation

Alright, here's the chill version:

Enums are like drop-downs for your variables.

Instead of writing:

let role = "ADMIN";

Which is risky (what if you typo it?), you write:

enum Role { Admin = "ADMIN", User = "USER", Guest = "GUEST" } let myRole: Role = Role.Admin;

Now TypeScript’s like:

“Yo, these are the only valid choices. Stick to 'em.”

It's basically:

  • You give each option a name

  • TypeScript handles the boring values (like 0, 1, "ADMIN")

  • Your code becomes cleaner and safer

Think of Enums like:

“Let’s name the options so you never have to remember weird codes like status = 1 again.”


🔥 Bonus Tip: Reverse Mapping (Numeric Enums Only)

enum Status { Ok = 200, NotFound = 404 } console.log(Status.Ok); // 200 console.log(Status[404]); // 'NotFound'

Enums let you go both ways — from name to value AND value to name (only in numeric enums).







8. Type Aliases vs Interfaces?

Ans.

✅ 1. Formal Definition with Example

🔸 Type Alias

A Type Alias is a way to give a custom name to any type — primitive, union, function, tuple, object, etc.

type User = { name: string; age: number; };

🔹 Interface

An Interface defines the shape of an object. It’s best used for objects and class-like structures and can be extended or merged.

interface User { name: string; age: number; }

✅ Key Differences

Featuretypeinterface
Can describe object types
Can describe primitives✅ (type Age = number)
Can describe tuples
Can extend other types✅ (type New = A & B)✅ (interface New extends A)
Can merge declarations✅ (interfaces merge)
Better for unions

💻 2. Code Examples + Explanation

✅ Example 1: Object Structure

type TypeUser = { name: string; age: number; }; interface InterfaceUser { name: string; age: number; }

Both are identical here.


✅ Example 2: Extending


// With type type Admin = TypeUser & { role: string }; // With interface interface AdminUser extends InterfaceUser { role: string; }

Both ways work, but interface uses extends while type uses &.


✅ Example 3: Declaration Merging (Only works with interfaces)


interface Box { height: number; } interface Box { width: number; } const b: Box = { height: 100, width: 200 }; // ✅ TS merges the two

This doesn’t work with type. Type aliases cannot be re-declared.


✅ Example 4: Union & Tuple Support (Only with type)

type Status = "success" | "error"; // ✅ works type Point = [number, number]; // ✅ tuple type // Not possible with interface

😎 3. Easy/Slang Explanation

Okay, here’s how you remember it:

🧱 Interface = Blueprint for Objects

Think of interface like a class blueprint:

interface User { name: string; age: number; }

You use it when you're working with objects, especially when building apps, libraries, or APIs.

Bonus: TS allows you to add more to the same interface later (aka declaration merging).


🎨 Type = Flexible Type Painter

type is like your wild creative tool — it can do objects too, but also:

  • Unions: "GET" | "POST"

  • Primitives: type ID = string | number

  • Tuples: [string, number]

It's flexible AF. Use it when:

  • You want to mix and match types

  • You need more than just plain object shapes


🔥 Quick Rule of Thumb

Use this when...Pick
You’re defining an object or a class shape✅ Interface
You need unions, primitives, or tuples✅ Type
You’re extending multiple types✅ Type
You want declaration merging✅ Interface

🧠 Interview Tip:

If asked in an interview:

"When should you use type vs interface?"

Say:

"Use interface for object shapes and when you expect the structure to be extended. Use type for everything else like primitives, unions, tuples, or when combining types with & or |."







9. Tuples?

Ans.

✅ 1. Formal Definition with Example

A Tuple in TypeScript is a typed array with a fixed number of elements, where each element has a specific type.

Unlike regular arrays (string[], number[]), tuples let you say:
“This array has exactly 2 elements — first is a string, second is a number.”


📌 Example:

let user: [string, number]; user = ["Ayan", 23]; // ✅ Valid user = [23, "Ayan"]; // ❌ Error: Type mismatch user = ["Ayan"]; // ❌ Error: Missing second element user = ["Ayan", 23, 100]; // ❌ Error: Too many elements

💻 2. Code Example + Explanation

function getUserInfo(): [string, number] { return ["Ayan", 23]; } const userInfo = getUserInfo(); const userName = userInfo[0]; // string const userAge = userInfo[1]; // number console.log(`Name: ${userName}, Age: ${userAge}`);

🧠 Explanation:

  • getUserInfo returns a tuple [string, number].

  • When you destructure or access elements, TypeScript knows the exact type of each index.

  • Prevents mistakes like swapping the order or mixing types.


😎 3. Easy/Slang Explanation

Imagine a tuple like a snack box with exactly two slots:

  • Slot 1: must have a sandwich (string)

  • Slot 2: must have a juice box (number — maybe size in oz)

You say:

“Hey, give me exactly a sandwich and a juice. No more, no less.”

If someone hands you two juices or a juice and a cookie, TypeScript throws a fit.


Tuples are super useful when you want to bundle a fixed set of different types together, like:

  • A username and userID [string, number]

  • A response status code and message [number, string]

  • Coordinates [number, number]






10. Type Assertions (as keyword)?

Ans.

✅ 1. Formal Definition with Example

Type Assertion allows you to override or specify the type of a value when TypeScript can’t infer it correctly, or when you know more about the type than the compiler.

It’s like saying:
“Hey TS, I’m certain this value is of this type, so treat it that way.”

Note: Type assertions do not perform any runtime type checking or conversion — they’re purely compile-time hints.


📌 Example:

let someValue: unknown = "Hello, TypeScript!"; // Assert that someValue is a string let strLength: number = (someValue as string).length;

Here, someValue is typed unknown, but you assert it as a string to access .length.


💻 2. Code Example + Explanation

function getElement(id: string) { return document.getElementById(id); } const input = getElement("my-input"); // TypeScript says input might be HTMLElement | null // We know it's an HTMLInputElement, so we assert: const inputElement = input as HTMLInputElement; console.log(inputElement.value);

🧠 Explanation:

  • getElementById returns HTMLElement | null.

  • But you know my-input is an <input> element, so you assert it as HTMLInputElement.

  • Without assertion, TypeScript would complain when you try to access .value.

  • Use assertions carefully! If you’re wrong, runtime errors may occur.


😎 3. Easy/Slang Explanation

Think of type assertion like telling your strict teacher:

“I know what I’m doing, trust me!”

Example:

let mystery: unknown = "yo"; let message = (mystery as string).toUpperCase();

TS was like:

“Hey, I don’t know what mystery is.”

You say:

“Chill, I’m telling you it’s a string.”


It’s like when you’re hacking the system:

“TS, stop guessing and just believe me for once.”

But don’t go crazy with this — if you lie and it’s not true, your app might break in the wild.


⚠️ Quick Tips:

  • Use only when you’re sure about the type.

  • Helps with DOM elements, third-party libraries, or complex scenarios.

  • Can be done with angle brackets (<string>value) but not recommended in TSX/React files (use as instead).







11. const, let, var differences (TS-specific cases)?

Ans.

✅ 1. Formal Definition with Example

  • var: Function-scoped or globally scoped variable. Can be re-declared and updated. Has hoisting issues.

  • let: Block-scoped variable. Can be updated but not re-declared in the same scope.

  • const: Block-scoped constant. Cannot be updated or re-declared. Must be initialized when declared.


TS-specific highlights:

  • const with literal values can be inferred as literal types instead of general types.

  • let and var are usually inferred with wider types.

  • TypeScript treats const variables with literal values as immutable types unless explicitly widened.


💻 2. Code Example + Explanation

const pi = 3.14; // type inferred as 3.14 (literal) let age = 30; // type inferred as number var username = "Ayan"; // type inferred as string // Trying to reassign: pi = 3.1415; // ❌ Error: Cannot assign to 'pi' because it is a constant age = 31; // ✅ Allowed username = "Ali"; // ✅ Allowed // Re-declaration: let age = 40; // ❌ Error: Cannot redeclare block-scoped variable 'age' var username = "Ali"; // ✅ Allowed (var allows re-declare in same scope) // Literal Type Example: const status = "success"; // status type is literally "success", not string let message = "hello"; // message type is string (wider type)

🧠 Explanation:

  • const pi = 3.14;
    TypeScript infers the literal type 3.14 (a special subtype of number). This means pi can only ever hold the exact value 3.14 unless you explicitly widen the type.

  • let age = 30;
    Inferred as number, can be updated but not re-declared in the same scope.

  • var username = "Ayan";
    Inferred as string, can be updated and re-declared, but has quirks like function-scoping and hoisting, so generally avoid it.

  • Re-declaration is allowed with var but not with let or const.

  • The key TS difference is literal type inference for const variables, useful for stricter type checks.


😎 3. Easy/Slang Explanation

Alright, here’s the scoop:

  • var is the old-school wild child — it’s function-scoped, hoisted, and lets you redeclare variables. It's like a reckless driver 🚗💨 — it can cause trouble, so TS doesn’t love it much.

  • let is the new cool kid on the block — block-scoped, no redeclares allowed, but you can update it. Like, “I’m chill, but don’t mess with me twice.”

  • const is the stick-to-your-word buddy — block-scoped and won’t let you change your mind once you set a value.

Now here’s the TypeScript twist:

When you write:

const mood = "happy";

TS locks that in as the exact word "happy", not just any string. So if you try:

mood = "sad"; // ❌ Nope, can't do that

But with let:

let mood = "happy"; // mood is just string (not "happy") mood = "sad"; // ✅ Allowed, no big deal

So const helps TS keep track of exact values and catch bugs if you try to change them.


🔥 Pro Tip:

If you want to widen a const literal to a general type, you can do:

const status: string = "success"; // now it's just string, not literal "success"






12. Type narrowing (typeof, in, instanceof)?

Ans.

✅ 1. Formal Definition with Example

Type Narrowing is the process by which TypeScript refines the type of a variable within a conditional block, based on runtime checks.

It lets the compiler know the exact type after a type guard check, so you can safely use properties or methods specific to that type.


💻 2. Common Type Guards

a) typeof Narrowing

Checks if a variable is of a primitive type: "string", "number", "boolean", "undefined", "object", or "function".

function formatId(id: number | string) { if (typeof id === "string") { console.log(id.toUpperCase()); // OK, id is string here } else { console.log(id.toFixed(2)); // OK, id is number here } }

b) in Narrowing

Checks if a property exists in an object.

type Admin = { name: string; role: string }; type User = { name: string; age: number }; function printInfo(person: Admin | User) { if ("role" in person) { console.log(`Admin role: ${person.role}`); // person is Admin here } else { console.log(`User age: ${person.age}`); // person is User here } }

c) instanceof Narrowing

Checks if an object is an instance of a class or constructor function.

class Dog { bark() { console.log("Woof!"); } } class Cat { meow() { console.log("Meow!"); } } function makeSound(animal: Dog | Cat) { if (animal instanceof Dog) { animal.bark(); // animal is Dog here } else { animal.meow(); // animal is Cat here } }

😎 3. Easy/Slang Explanation

Type narrowing is like TypeScript putting on detective glasses 🔍 and saying:

“Okay, based on what you just told me, I now know exactly what type this is inside here.”

  • With typeof, it’s like checking if your phone is an iPhone or an Android. If it’s an iPhone (string), you use iPhone tricks; if Android (number), Android tricks.

  • With in, it’s like asking,

“Does this person have a ‘role’ badge? Then they’re an Admin. Otherwise, they’re a regular User.”

  • With instanceof, it’s like checking if your pet is a Dog or Cat so you can call the right sound.


🔥 Why use type narrowing?

Without narrowing, TS forces you to handle all possible types safely, which can be clunky:

function process(id: string | number) { console.log(id.toString()); // Safe, because both have toString() // But if you want to do id.toFixed(), TS blocks you unless you narrow first }

Narrowing gives you freedom to access specific stuff safely.























------------------------------------------------------------------------------------------------------------

📦 2. Functions and Objects




1. Function Types (with params & return types)?

Ans.

🧾 1. Formal Definition

In TypeScript, Function Types are used to define the structure of a function, specifying:

  • What parameters it accepts (and their types),

  • What type of value it returns.

This helps catch errors early and improves code clarity and maintainability.

✅ Example:
A function that takes two number parameters and returns a number:


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

💻 2. Code Example + Explanation

// Declaring a function type let greet: (name: string, age: number) => string; // Assigning a function matching the type greet = (name, age) => { return `Hello, ${name}. You are ${age} years old.`; }; console.log(greet("Ayan", 22)); // Output: Hello, Ayan. You are 22 years old.

🔍 Explanation:

  • (name: string, age: number) => string is the function type.

  • It means: this function takes a string and a number, and returns a string.

  • We then assign an arrow function to greet which matches that structure.

  • console.log(...) outputs the returned string.


🧃 3. Easy/Slang Explanation

Alright, here’s the chill version:

So imagine you’re telling TypeScript,
“Yo bro, I’m gonna make a function, and I promise it’ll take a name (text) and age (number), and return a string back to ya.”

Function types are just contracts. You’re saying:

“If I get this kind of input, I’ll give you this kind of output.”

It’s like giving your function a rulebook:

  • “Only accept name and age”

  • “Return me a string sentence”

If you try to break the rule (like pass a boolean or forget to return a string), TypeScript will throw hands (aka errors 💥).






2. Optional and Default Parameters?

Ans.

🧾 1. Formal Definition

In TypeScript, optional parameters allow you to define function parameters that are not required when calling the function.
They are marked using a ? after the parameter name.

Default parameters allow you to assign a default value to a parameter in case it is not provided by the caller.

✅ Example:

function greet(name: string, age?: number): string { return age ? `Hi, ${name}. You are ${age} years old.` : `Hi, ${name}.`; } function welcome(user: string = "Guest"): string { return `Welcome, ${user}!`; }

💻 2. Code Example + Explanation

function introduce(name: string, hobby: string = "coding", country?: string): string { if (country) { return `${name} loves ${hobby} and lives in ${country}.`; } else { return `${name} loves ${hobby}.`; } } console.log(introduce("Ayan")); // Output: Ayan loves coding. console.log(introduce("Ayan", "gaming")); // Output: Ayan loves gaming. console.log(introduce("Ayan", "gaming", "Poland")); // Output: Ayan loves gaming and lives in Poland.

🔍 Explanation:

  • hobby: string = "coding"Default Parameter: if no value is passed, it uses "coding" by default.

  • country?: stringOptional Parameter: can be omitted without causing an error.

  • The function returns different strings based on whether the optional country is provided or not.


🧃 3. Easy/Slang Explanation

Alright, here’s the chill talk:

Let’s say you’re building a function that introduces someone.
But sometimes you don’t know their country, or maybe they didn’t tell you their hobby. No problem!

TypeScript lets you say:

“Yo, if you don’t give me that value, I’ll either use a backup (default), or just skip it (optional).”

  • Optional (?) = “It’s cool if you don’t give this one.”

  • Default (=) = “If you don’t give it, I’ll just use this value instead.”

So your function is smart enough to work even when people don’t give all the info. Pretty handy, right?






3. Rest Parameters & Overloads?

Ans.

🔹 1. Formal Definition

Rest Parameters

In TypeScript, rest parameters allow a function to accept an unlimited number of arguments as an array.
They are defined using the ... (spread syntax) before the parameter name, and must always be the last parameter.

✅ Example:

function sum(...numbers: number[]): number { return numbers.reduce((total, n) => total + n, 0); }

Function Overloads

Function overloading in TypeScript means defining multiple function signatures for a single function, so it can handle different argument types or counts in a type-safe way.

✅ Example:

function greet(person: string): string; function greet(age: number): string; function greet(arg: string | number): string { return typeof arg === "string" ? `Hello, ${arg}` : `You are ${arg} years old`; }

💻 2. Code Example + Explanation

🧮 Rest Parameters

function multiplyAll(multiplier: number, ...numbers: number[]): number[] { return numbers.map((num) => num * multiplier); } console.log(multiplyAll(2, 1, 2, 3)); // [2, 4, 6]

🔍 Explanation:

  • multiplier: number → normal param

  • ...numbers: number[] → rest parameter; collects all remaining args into an array

  • .map() → multiplies each number by the multiplier


🧩 Function Overloads

function combine(a: number, b: number): number; function combine(a: string, b: string): string; function combine(a: any, b: any): any { return a + b; } console.log(combine(2, 3)); // 5 console.log(combine("Hi, ", "Ayan")); // Hi, Ayan

🔍 Explanation:

  • First two lines: overload signatures—tell TypeScript what types the function can handle.

  • Third line: actual implementation that handles both types using any.

  • Based on what you pass, TypeScript picks the right signature.


🧃 3. Easy/Slang Explanation

🤹 Rest Parameters

Okay, imagine your function is like a collector that’s chill with however many arguments you throw at it.

“Yo, just give me as many numbers as you want, I’ll pack 'em in a bag (array) and handle them all!”

So when you use ...numbers, TypeScript is like:

“Cool, all those extras? I got this.”


🌀 Function Overloads

Now for overloads, think of it like this:

“Same function, different vibes.”

You want one function to behave differently based on what you pass in—like a multilingual person who replies differently depending on the language you speak.

So you're basically saying:

“If I pass strings, do this. If I pass numbers, do that.”

TypeScript’s overloads make your function versatile but still type-safe. No wild guesses.






4. Function type alias?

Ans.

🧾 1. Formal Definition

In TypeScript, a Function Type Alias is a way to give a name to a function's type structure (its parameters and return type).
This makes your code reusable, readable, and clean, especially when the same function shape is used in multiple places.

You define a function type alias using the type or interface keyword.

✅ Example:

type GreetFn = (name: string) => string;

Now any function that matches this structure can be assigned to GreetFn.


💻 2. Code Example + Explanation

// Step 1: Define the function type alias type MathOperation = (a: number, b: number) => number; // Step 2: Use the alias for different functions const add: MathOperation = (x, y) => x + y; const multiply: MathOperation = (x, y) => x * y; console.log(add(5, 3)); // 8 console.log(multiply(5, 3)); // 15

🔍 Explanation:

  • type MathOperation = (a: number, b: number) => number → defines a function type alias.

  • add and multiply must follow that structure: 2 numbers in, 1 number out.

  • Makes your code type-safe and consistent across functions.


🧃 3. Easy/Slang Explanation

Alright, think of a function type alias like a nickname for a kind of function.

Instead of rewriting the full parameter and return type over and over like a robot 🤖, you just say:

“Hey TypeScript, let’s call this kind of function MathOperation. Every time I say MathOperation, I mean a function that takes two numbers and returns a number.”

So now, when you build a bunch of math functions (like add, subtract, multiply), you can just stamp them with MathOperation and you’re golden. 🔥

It’s like creating your own shortcut that TypeScript understands.







5. Callbacks with type?

Ans.

🧾 1. Formal Definition

A callback is a function passed as an argument to another function, and it is usually invoked inside the outer function to complete some kind of action.

In TypeScript, you can provide an explicit type for the callback, including:

  • Its parameters and return type

  • Whether it's optional

  • Its overall structure

✅ Example:

function doSomething(callback: () => void) { callback(); }

Here, callback is a parameter with the type of a function that takes no arguments and returns nothing (void).


💻 2. Code Example + Explanation

// Define a function that accepts a callback with typed parameters and return function processUserInput(input: string, callback: (data: string) => void): void { const formatted = input.trim().toUpperCase(); callback(formatted); } // Call the function with a properly typed callback processUserInput(" ayan ", (result) => { console.log("Processed:", result); });

🔍 Explanation:

  • callback: (data: string) => void means:

    • The callback must accept a single string parameter (data)

    • And it must return void (nothing)

  • Inside processUserInput, we call callback(formatted)

  • When we use processUserInput(...), we pass a function that logs the result

This ensures that any callback passed must match the expected type — helping prevent bugs and type mismatches.


🧃 3. Easy/Slang Explanation

Okay, so imagine you’re calling your buddy and saying:

“Yo, once you're done cleaning up the string, call me back and tell me what it looks like.”

That “call me back” part is the callback.

Now TypeScript’s like your strict assistant who’s like:

“Alright, if someone’s calling you back, tell me what they’re gonna say and what they’ll give you.”

So you type the callback like:

  • “They’re gonna give me a string”

  • “And I don’t expect them to return anything back (void)”

This keeps everyone on the same page and avoids those awkward “Uh... I wasn’t expecting that” moments.





6. Object Types & Index Signatures?

Ans.

🧾 1. Formal Definition

Object Types

An object type in TypeScript defines the shape of an object, meaning:

  • What properties it has

  • What types those properties should be

You can also define optional properties and method types inside object types.

✅ Example:

type User = { name: string; age: number; };

Index Signatures

An index signature allows you to define a type for dynamic property keys—useful when you don’t know the exact property names ahead of time but you know the type of the keys and values.

✅ Example:


type Scores = { [subject: string]: number; };

This means: any property with a string key will have a number value.


💻 2. Code Example + Explanation

🧱 Object Type Example

type Product = { id: number; name: string; price: number; inStock?: boolean; // Optional property }; const item: Product = { id: 1, name: "Keyboard", price: 49.99, };

🔍 Explanation:

  • Product defines the shape of the object.

  • inStock? is optional, meaning it’s okay if the object doesn’t include it.

  • item must follow the Product type—no extra properties, and all types must match.


🔑 Index Signature Example


type Settings = { [key: string]: string | number; }; const config: Settings = { theme: "dark", fontSize: 14, layout: "grid" };

🔍 Explanation:

  • The type says: any string key is allowed, and the value must be string or number.

  • This is helpful when object keys are dynamic or not known in advance.

  • Without index signatures, TypeScript would throw an error for unknown keys.


🧃 3. Easy/Slang Explanation

💡 Object Types

Imagine you're designing a character in a video game, and you're like:

“Yo, every player’s gotta have a name and level.”

You create a Player type:

type Player = { name: string; level: number; };

Now any object that’s a Player better follow that rulebook or TypeScript will yell at you. 📢


🧠 Index Signatures

This one’s like saying:

“I don’t know what keys this object will have, but whatever they are, they better follow this rule.”

So it's like giving TypeScript a heads-up:

“Trust me, all the keys will be strings, and all the values will be either strings or numbers.”

Perfect when you're working with settings, scores, error messages, or user-generated content.







7. Readonly Properties?

Ans.

🧾 1. Formal Definition

In TypeScript, a readonly property is a property of an object that cannot be modified after the object is created.
You use the readonly keyword to ensure that once a value is assigned, it cannot be changed.

This is useful for creating immutable data structures, which makes your code safer and more predictable.

✅ Example:

type User = { readonly id: number; name: string; };

In this case, the id is read-only — it can be set once and never updated.


💻 2. Code Example + Explanation

type Car = { readonly vin: string; // Vehicle Identification Number - can't change model: string; }; const myCar: Car = { vin: "1HGCM82633A004352", model: "Civic", }; myCar.model = "Accord"; // ✅ allowed myCar.vin = "XYZ1234567"; // ❌ Error: Cannot assign to 'vin' because it is a read-only property

🔍 Explanation:

  • readonly vin: string means: you can set vin when creating myCar, but not modify it after.

  • Trying to reassign vin will give a compile-time error.

  • You can still change model since it's not read-only.


🧃 3. Easy/Slang Explanation

Alright, here’s the chill version:

Think of readonly like putting a 🔒 lock on a property.

You’re telling TypeScript:

“Hey bro, once I set this thing — don’t let anyone, not even me, mess with it again.”

So if you have something like a user ID, order number, or car VIN — stuff that should never change once set — slap a readonly on it.

It’s like:

“Here’s your value. It’s sacred now. No take-backs.” 🙅‍♂️






8. Destructuring with types?

Ans.

🧾 1. Formal Definition

Destructuring with types in TypeScript allows you to extract values from objects or arrays while also annotating types for the destructured variables.

You can:

  • Destructure objects and arrays

  • Annotate types during destructuring or assign types before destructuring

✅ Example:

const user = { name: "Ayan", age: 22 }; const { name, age }: { name: string; age: number } = user;

💻 2. Code Example + Explanation

✅ Object Destructuring with Type Annotation

function displayUser({ name, age }: { name: string; age: number }): void { console.log(`${name} is ${age} years old`); } displayUser({ name: "Ayan", age: 22 });

🔍 Explanation:

  • { name, age } is destructuring the object passed to the function.

  • : { name: string; age: number } explicitly types the structure of the input.

  • TypeScript checks that the object passed has name as a string and age as a number.


✅ Array Destructuring with Type Annotation

const rgb: [number, number, number] = [255, 100, 50]; const [r, g, b]: [number, number, number] = rgb; console.log(`Red: ${r}, Green: ${g}, Blue: ${b}`);

🔍 Explanation:

  • rgb is a tuple of three numbers.

  • [r, g, b]: [number, number, number] assigns each value and makes sure they’re all numbers.

  • Destructuring and typing happen together.


🧃 3. Easy/Slang Explanation

Alright, imagine destructuring like opening a box and grabbing what you need:

“Yo, I got an object with a bunch of stuff, but I only want name and age. Lemme just pull those out.”

So you're like:

const { name, age } = user;

But TypeScript being the grammar police says:

“Cool, but tell me what type those are!”

So you add:

const { name, age }: { name: string; age: number } = user;

Same with arrays:

“I got a list, and I’mma grab the first few values — and btw, they’re all numbers.”

Destructuring with types is just:

“Grab only what I want, and make sure the types are tight.”


















------------------------------------------------------------------------------------------------------------

📚 3. Advanced Types




1. Interfaces vs Type Aliases (deep dive)?

Ans.

🧾 1. Formal Definition

🔹 Type Alias (type)

A type alias lets you create a new name for any type — primitives, unions, tuples, functions, objects, etc. It’s very flexible.

✅ Example:

type UserID = string | number;

You can use type to define object shapes too:

type User = { name: string; age: number; };

🔹 Interface

An interface is specifically designed to define the shape of an object. It can define properties and method signatures.
It also supports declaration merging and extension.

✅ Example:

interface User { name: string; age: number; }

💻 2. Code Examples + Explanations

✅ Basic Object Types (They look similar here)

type PersonType = { name: string; age: number; }; interface PersonInterface { name: string; age: number; }

🔍 Explanation:

Both define an object with name and age. Functionally, these are the same for most use cases.


✅ Extending

// Interface can extend interface Animal { name: string; } interface Dog extends Animal { breed: string; } // Type can also extend (but uses `&`) type Cat = { name: string; }; type PersianCat = Cat & { fluffiness: number; };

🔍 Explanation:

  • interface Dog extends Animal is clean and semantic.

  • type PersianCat = Cat & { ... } is more “math-style” (intersection).


✅ Declaration Merging (Only Interfaces can do this)

interface Box { size: number; } interface Box { color: string; } // Box is now: { size: number; color: string } const myBox: Box = { size: 10, color: "red" };

🔍 Explanation:

  • Interfaces merge if declared multiple times. This is useful in libraries and window extensions.

  • type cannot be redeclared like this — you’ll get an error.


✅ Function Types (Only type supports directly)

type Greet = (name: string) => string; const hello: Greet = (name) => `Hello, ${name}`;

🔍 Explanation:

  • type works great for function types.

  • interface can do it too, but with a slightly clunky syntax:

interface GreetFn { (name: string): string; }

✅ Tuple and Union Support (Only type)

type RGB = [number, number, number]; // Tuple type Status = "loading" | "success" | "error"; // Union

🔍 Explanation:

  • type can describe tuples, unions, intersections, primitives, etc.

  • interface can only describe objects.


🧃 3. Easy/Slang Explanation

Okay, here’s the real talk 😎

🔹 interface is like the polite guy in the team:

“Hey, I’m all about shaping objects and playing nice with others. I can even be extended and merged if someone wants to add more stuff to me.”

You mostly use interface for:

  • Object shapes

  • Class contracts

  • Clean inheritance (extends)

  • Libraries and public APIs


🔸 type is the flexible genius:

“Bro, I can be anything — object, string, number, tuple, union, function. I do it all.”

You use type for:

  • Union types (e.g., "light" | "dark")

  • Tuples ([number, string])

  • Functions ((x: number) => string)

  • Combining types (&, |)

  • Basically — power user stuff


🥊 When to Use What?

ScenarioUse interfaceUse type
Object shape    ✅ Yes    ✅ Yes
Extending/implementing objects    ✅ Best choice    Possible with &
Function types    ⚠️ Clunky    ✅ Clean & concise
Union & intersection types    ❌ Not supported    ✅ Perfect fit
Tuples, arrays, primitives    ❌ No    ✅ Can do it all
Declaration merging    ✅ Yes (superpower!)    ❌ Error if repeated







2. Type Extensions (extends)?

Ans.

🧾 1. Formal Definition

Type Extension using extends allows you to create a new type or interface by building on top of an existing one.
This helps you avoid repeating properties and encourages reusability and modular design.

In TypeScript:

  • interface uses extends to inherit from other interfaces

  • type uses intersection (&) for a similar effect

✅ Example (interface):

interface Person { name: string; age: number; } interface Employee extends Person { jobTitle: string; }

Now Employee has all properties of Person + jobTitle.


💻 2. Code Example + Explanation

✅ Using extends with Interface

interface BaseUser { id: number; username: string; } interface AdminUser extends BaseUser { role: "admin"; } const admin: AdminUser = { id: 1, username: "AyanDev", role: "admin", };

🔍 Explanation:

  • AdminUser extends BaseUser, so it inherits id and username.

  • We add a custom role field specific to admins.

  • This keeps your types modular and scalable as your app grows.


✅ Type Alias "Extension" with & (Intersection)

type Product = { name: string; price: number; }; type DiscountedProduct = Product & { discountPercent: number; }; const saleItem: DiscountedProduct = { name: "Keyboard", price: 49.99, discountPercent: 10, };

🔍 Explanation:

  • DiscountedProduct combines Product and another object with discountPercent.

  • This is how type aliases simulate extension using intersection (&).

  • The result is a merged type with all properties from both.


🧃 3. Easy/Slang Explanation

Alright, real talk — this is like copy-pasting properties from one type into another, but with style. 😎

When you say:

interface Admin extends User {}

You're basically saying:

“Yo, give me everything User has… and I’mma add some admin stuff on top.”

It’s like inheritance for types. Clean, powerful, and no repeating code.


When you use type, you don’t say extends, you use &:

type Dog = Animal & { breed: string }

That’s like saying:

“Take all the Animal stuff, and glue this new stuff to it.”


Quick Summary:

Use Caseinterface extendstype & type
Object inheritance✅ Yes✅ Yes (with &)
Clean class-style syntax✅ Yes❌ No
Extend multiple sources✅ Yes✅ Yes (e.g. A & B & C)
Works with functions, unions, tuples❌ Limited✅ Very flexible







3. keywords -> 'keyof', 'typeof', 'in', 'infer', 'as'?

Ans.

✅ 1. keyof

🧾 Formal Definition

keyof is a TypeScript operator that returns the union of property names (keys) of a given type.

✅ Example:

type User = { name: string; age: number }; type UserKeys = keyof User; // "name" | "age"

💻 Code Example + Explanation

type Car = { make: string; model: string; year: number; }; type CarKeys = keyof Car; // "make" | "model" | "year" function getValue(obj: Car, key: CarKeys) { return obj[key]; }

🔍 Explanation:

  • keyof Car returns a union of keys: "make" | "model" | "year".

  • The getValue function takes any valid property key of Car.


🧃 Slang

keyof is like saying:

“Give me a list of the keys of this object — just the names, not the values.”

Think of it as TypeScript peeking inside your object and listing the keys like a menu.


✅ 2. typeof

🧾 Formal Definition

typeof in TypeScript is used to get the type of a variable, function, or constant.

✅ Example:

const user = { name: "Ayan", age: 22 }; type UserType = typeof user; // { name: string; age: number }

💻 Code Example + Explanation

const rgb = [255, 0, 0]; function logColor(color: typeof rgb) { console.log("Color:", color); }

🔍 Explanation:

  • typeof rgb captures the exact type of the rgb variable.

  • Useful when you don’t want to manually retype an existing shape.


🧃 Slang

typeof is like telling TypeScript:

“Hey, just look at this variable and figure out its type for me.”

Basically:

“Yo TS, copy-paste the type from this thing over here.”


✅ 3. in

🧾 Formal Definition

in is used in mapped types to iterate over keys — often with keyof.

✅ Example:

type User = { name: string; age: number }; type OptionalUser = { [K in keyof User]?: User[K]; };

💻 Code Example + Explanation

type Status = "pending" | "approved" | "rejected"; type StatusFlags = { [S in Status]: boolean; }; // Result: // { // pending: boolean; // approved: boolean; // rejected: boolean; // }

🔍 Explanation:

  • S in Status loops over each union member ("pending", "approved", etc.)

  • It creates a key for each value with boolean as the type.


🧃 Slang

in is like TypeScript’s way of saying:

“Let me loop through each of these keys and build a new object.”

It’s like a map function — for types.


✅ 4. infer

🧾 Formal Definition

infer is used in conditional types to extract and name a type from another type.

✅ Example:

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

💻 Code Example + Explanation

type MyFunc = () => number; type ReturnTypeOfMyFunc = MyFunc extends (...args: any[]) => infer R ? R : never; // ReturnTypeOfMyFunc is number

🔍 Explanation:

  • infer R means: "try to figure out the return type R"

  • If T is a function, it extracts the return type.

  • If not, it returns never.


🧃 Slang

infer is like TypeScript saying:

“Let me guess what this type is, and I’ll name it for you.”

It’s Sherlock Holmes mode for types:

“I see you're a function... I infer your return type is number.”


✅ 5. as

🧾 Formal Definition

as is a type assertion — it tells TypeScript to treat a value as a specific type, even if TypeScript can’t be sure of it.

✅ Example:

let val: unknown = "hello"; let len = (val as string).length;

💻 Code Example + Explanation

const input = document.getElementById("username") as HTMLInputElement; input.value = "Ayan";

🔍 Explanation:

  • document.getElementById(...) returns HTMLElement | null.

  • We assert that it’s specifically an HTMLInputElement, so we can safely access .value.


🧃 Slang

as is like saying:

“Trust me bro, I know what I’m doing.”

You're telling TypeScript:

“Hey, I swear this thing is a string (or whatever) — let me treat it that way.”

But use it wisely — you're overriding TypeScript’s brain. 🧠⚠️


💥 Quick Recap Table

KeywordWhat it doesThink of it like...
keyof    Gets keys of a type    “Give me the keys” 🔑
typeof    Gets type from a value    “Copy the type” 📋
in    Loops through keys for mapping    “Type-level for loop” 🔁
infer    Extracts a type in conditionals    “Guess and name it” 🕵️
as    Type assertion    “Trust me, bro” 😅







4. Mapped Types?

Ans.

🧾 1. Formal Definition

A Mapped Type in TypeScript lets you create new types by transforming existing ones — typically by looping over their keys using in + keyof.

You can:

  • Make all properties optional

  • Readonly

  • Change value types

  • Or even filter/rename keys (with advanced tricks)

✅ Syntax:

type NewType = { [K in keyof ExistingType]: Transform<ExistingType[K]> };

✅ Example:

type User = { name: string; age: number }; type OptionalUser = { [K in keyof User]?: User[K]; };

💻 2. Code Example + Explanation

// Original type type User = { id: number; name: string; email: string; }; // Mapped type to make all properties readonly type ReadonlyUser = { [K in keyof User]: Readonly<User[K]>; }; // Mapped type to make all properties optional type PartialUser = { [K in keyof User]?: User[K]; }; // Mapped type to convert all values to boolean type BooleanFlags = { [K in keyof User]: boolean; }; // Usage const userFlags: BooleanFlags = { id: true, name: false, email: true, };

🔍 Explanation:

  • [K in keyof User] loops through all keys of User ("id", "name", "email").

  • User[K] accesses the type of that property (e.g., number, string, etc.).

  • You can apply transformations like Readonly<...>, ?, or change value types (like setting to boolean).


🧃 3. Easy/Slang Explanation

Alright, real talk:

Mapped types are like:

“Loop through each key in a type and apply some rule to it.”

It's like telling TypeScript:

“Yo, for every property in User, make it optional / readonly / boolean / whatever I say.”

Imagine you’ve got a regular object type, and you wanna clone it — but with a twist. Mapped types let you:

  • Make a “readonly clone” (Readonly<T>)

  • Make a “optional clone” (Partial<T>)

  • Or your own “boolean version” (e.g., feature flags)

It's like using a for loop on types — you're mapping over the keys and building something new.


🧠 Bonus Tip: Built-in Mapped Types

TypeScript gives you some mapped types out of the box:

  • Partial<T> → makes everything optional

  • Required<T> → makes everything required

  • Readonly<T> → makes everything readonly

  • Pick<T, K> → picks only certain keys

  • Record<K, T> → builds an object with specific keys and a uniform value type


type Flags = Record<"darkMode" | "betaUser", boolean>; // { darkMode: boolean; betaUser: boolean }







5. Conditional Types?

Ans.

🧾 1. Formal Definition

Conditional Types in TypeScript allow you to define types based on a condition, just like if...else but for types.

✅ Syntax:

T extends U ? X : Y

This means:

  • If type T is assignable to type U, then the result is type X

  • Otherwise, it is type Y

✅ Example:

type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true type B = IsString<123>; // false

💻 2. Code Example + Explanation

// Define a conditional type type ResponseType<T> = T extends string ? "Text" : T extends number ? "Number" : "Unknown"; // Use it type A = ResponseType<"hello">; // "Text" type B = ResponseType<42>; // "Number" type C = ResponseType<boolean>; // "Unknown"

🔍 Explanation:

  • ResponseType<T> uses nested conditional types

  • If T is a string, the type is "Text"

  • Else if it’s a number, the type is "Number"

  • Else, it’s "Unknown"

Conditional types help your types make decisions based on what you pass in.


🧃 3. Easy/Slang Explanation

Imagine conditional types like:

"If this type is that, then do this. Otherwise, do that."

It's like an if-else statement, but for types.

💡 Example:

type IsCool<T> = T extends "Ayan" ? true : false;

This is TypeScript saying:

“If T is 'Ayan', then it’s true. Otherwise, nope.”

So:

type Result = IsCool<"Ayan">; // true type Result2 = IsCool<"John">; // false

You can even use them to extract types, filter types, or dynamically return different shapes. Conditional types give your types brains. 🧠


💥 Bonus: Real-World Use Case

🔧 Extract Return Type of a Function

type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never; type Fn = () => number; type Result = GetReturn<Fn>; // number

Here:

  • infer R means “try to guess the return type”

  • GetReturn<Fn> extracts the number type from () => number







6. Utility Types (Partial, Required, Pick, Omit, Record, Exclude, Extract, NonNullable)?

Ans.

🧾 1. Formal Definition

Utility Types are pre-built generic helpers provided by TypeScript to transform or work with other types.
They help make your code DRY, readable, and dynamic by enabling type manipulation without repeating yourself.

Here are the key built-in utility types we’ll cover:

UtilityPurpose
Partial<T>    Makes all properties in T optional
Required<T>    Makes all properties in T required
Pick<T, K>    Creates a type by picking specific keys K
Omit<T, K>    Creates a type by omitting keys K
Record<K, T>    Creates a type with keys K and values of type T
Exclude<T, U>    Excludes from T those types assignable to U
Extract<T, U>    Extracts from T those types assignable to U
NonNullable<T>    Removes null and undefined from T

💻 2. Code Examples + Explanation

1️⃣ Partial<T>

type User = { id: number; name: string }; type PartialUser = Partial<User>; // All fields optional const u: PartialUser = { name: "Ayan" };

👉 Makes every property optional (?).


2️⃣ Required<T>

type User = { id?: number; name?: string }; type FullUser = Required<User>; // All fields must be present now const u: FullUser = { id: 1, name: "Ayan" };

👉 Opposite of Partial — forces all optional fields to be required.


3️⃣ Pick<T, K>

type User = { id: number; name: string; email: string }; type UserInfo = Pick<User, "id" | "name">; // Only id and name allowed const u: UserInfo = { id: 1, name: "Ayan" };

👉 Extract specific keys from a type.


4️⃣ Omit<T, K>

type User = { id: number; name: string; email: string }; type WithoutEmail = Omit<User, "email">; // id and name only const u: WithoutEmail = { id: 2, name: "Haider" };

👉 Remove specified keys.


5️⃣ Record<K, T>

type Flags = Record<"darkMode" | "betaUser", boolean>; // All keys must be present with boolean values const settings: Flags = { darkMode: true, betaUser: false };

👉 Creates a type with fixed keys and uniform value types.


6️⃣ Exclude<T, U>

type Status = "success" | "error" | "loading"; type NotLoading = Exclude<Status, "loading">; // Only "success" or "error" const s: NotLoading = "error";

👉 Removes specific types from a union.


7️⃣ Extract<T, U>

type Mixed = string | number | boolean; type OnlyStringOrNumber = Extract<Mixed, string | number>; // "string" | "number"

👉 Keeps only types assignable to the second type.


8️⃣ NonNullable<T>

type Maybe = string | null | undefined; type Definitely = NonNullable<Maybe>; // string

👉 Filters out null and undefined.


🧃 3. Easy/Slang Explanation

Think of utility types as TypeScript’s cheat codes for modifying types fast:

  • 🧩 Partial<T>: “Make everything optional”

  • 🔒 Required<T>: “Nah bro, give me all the properties back!”

  • ✂️ Pick<T, K>: “Just give me these keys only”

  • 🚫 Omit<T, K>: “Give me everything except these keys”

  • 🗂️ Record<K, T>: “I want an object with these keys and same type for all values”

  • Exclude<T, U>: “Remove these types from the union”

  • Extract<T, U>: “Keep only these types from the union”

  • 🚿 NonNullable<T>: “Get rid of null and undefined — no maybes allowed!”


📌 Summary Table

UtilityWhat it does
Partial<T>    { a?: T }
Required<T>    { a: T } (even if originally optional)
Pick<T, K>        Only selected keys
Omit<T, K>        Everything except selected keys
Record<K, T>        Map keys to a uniform value type
Exclude<T, U>        T without U
Extract<T, U>        T with only U
NonNullable<T>        Remove null & undefined







7. Discriminated Unions?

Ans.

🧾 1. Formal Definition

Discriminated Unions (also called Tagged Unions) are a pattern in TypeScript that combines:

  1. Union types

  2. Common "discriminant" property (usually a string literal)

  3. Type narrowing using that property

This makes it super easy and type-safe to distinguish between different object types inside a union.

✅ Structure:

type A = { kind: "a", data: string } type B = { kind: "b", data: number } type AB = A | B

Here, "kind" is the discriminant.


💻 2. Code Example + Explanation

type Circle = { kind: "circle"; radius: number; }; type Square = { kind: "square"; side: number; }; type Shape = Circle | Square; function getArea(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.side * shape.side; } }

🔍 Explanation:

  • The Shape union combines both Circle and Square.

  • Both types share a kind property with literal values.

  • TypeScript uses shape.kind to narrow the object type inside the switch.

  • When kind is "circle", TS knows it's a Circle and allows access to radius.

🧠 This is super powerful because it avoids manual type casting or typeof hacks.


🧃 3. Easy/Slang Explanation

Think of discriminated unions like a group of people wearing name tags.

type Circle = { kind: "circle", radius: 5 }; type Square = { kind: "square", side: 4 };

Now imagine a function that says:

"Hey, if your tag says ‘circle’, I’ll use your radius. If it says ‘square’, I’ll use your side."

It's like:

switch (person.kind) { case "circle": // You're a circle! Cool, I know what to do. }

TL;DR:

You give each type a little badge (kind), and TypeScript uses that to figure out who’s who and what they can do. No confusion, no errors, just smooth code. 🔥


✅ Summary

ConceptRole
kind field    Discriminant/tag
`Shape = A    B
switch (shape.kind)    Type narrowing (safe & automatic)
Usage    Pattern matching in a clean way 💯






8. Template Literal Types?

Ans.

🧾 1. Formal Definition

Template Literal Types in TypeScript allow you to create new string types by combining literal strings and union types — using the same backtick syntax (`) as JavaScript template literals.

They are statically evaluated at compile-time and are useful for modeling patterns like CSS classes, file names, event types, etc.

✅ Syntax:

type Greeting = `Hello, ${string}`;

Here, Greeting can be any string that starts with "Hello, " followed by any string.


💻 2. Code Example + Explanation

Example 1: Basic Template Literal

type First = "dark"; type Second = "mode"; type Combined = `${First}-${Second}`; // "dark-mode" const theme: Combined = "dark-mode"; // ✅ Valid // const theme2: Combined = "mode-dark"; ❌ Error

🔍 Explanation:

  • We use the template literal syntax: `${...}-${...}`.

  • It statically combines two string literal types into one.

  • Combined becomes "dark-mode" — a single specific string type.

  • Any string that doesn’t exactly match "dark-mode" will be rejected.


Example 2: Union + Template Literal

type Lang = "en" | "pl"; type Page = "home" | "about"; type Route = `/${Lang}/${Page}`; const r1: Route = "/en/home"; // ✅ const r2: Route = "/pl/about"; // ✅ // const r3: Route = "/es/home"; // ❌ not allowed

🔍 Explanation:

  • TypeScript will cross all combinations of "en" | "pl" and "home" | "about".

  • It generates:

    • "/en/home", "/en/about", "/pl/home", "/pl/about"

  • Great for route typing, event names, keys, etc.


🧃 3. Easy / Slang Explanation

Imagine you're playing with string LEGOs 🧱.

You take:

  • A "dark" LEGO block

  • A "mode" LEGO block

  • You snap them together with a - and BOOM → "dark-mode"

Template Literal Types = building new string types by piecing together existing string types.

Just like JavaScript string templates:

`Hello, ${name}`

But for types:

type Hello = `Hello, ${string}`

TypeScript even auto-generates combos when you use unions:

type Btn = `btn-${"primary" | "secondary"}`; // btn-primary | btn-secondary

It’s like telling TypeScript:

“Hey, go make all the strings that look like this pattern, and lock them down as types.”


✅ Summary Table

ConceptExampleMeaning
Basic Template Type    `btn-${string}`    Any btn-* string
Combining literals    `${"on"    "off"}-click`
Route generation    `/${"en"    "pl"}/${"home"
Pattern enforcement    Used in APIs, routes, keys, CSS    Restrict to specific patterns






9. Recursive Types?

Ans.

🧾 1. Formal Definition

Recursive Types are types that refer to themselves as part of their definition. This allows modeling of nested or self-similar data structures like trees, linked lists, nested comments, menus, etc.

✅ A recursive type is defined by referencing its own name within the type definition.

Example:

type Category = { name: string; subcategories?: Category[]; // Reference to itself! };

💻 2. Code Example + Explanation

Example: Nested Comment Thread

type Comment = { id: number; text: string; replies?: Comment[]; // Recursive part }; const discussion: Comment = { id: 1, text: "This is the first comment", replies: [ { id: 2, text: "This is a reply", replies: [ { id: 3, text: "Nested reply!", }, ], }, ], };

🔍 Explanation:

  • Comment has an optional replies field.

  • replies is an array of the same Comment type — that's recursion!

  • This allows deeply nested levels of replies, perfect for real-world data like Reddit threads, Facebook comments, etc.

Another common recursive pattern: Tree structures.

type TreeNode = { value: number; children?: TreeNode[]; };

🧃 3. Slang / Easy Explanation

💬 “Ayo, this is just a type that keeps calling itself like a mirror in a mirror.”

Think of it like Russian nesting dolls:

  • Each doll can contain another doll inside.

  • Same shape, just nested over and over.

So when TypeScript sees:

type Menu = { name: string; subMenu?: Menu[] }

It’s like:

“Yo TS, just keep going deeper into this same structure if you see a submenu.”

It’s perfect for:

  • 🔁 Menus

  • 🌳 Trees

  • 💬 Comment threads

  • 🧵 Chat replies


✅ Summary

ConceptDescription
Recursive typeA type that references itself
Syntaxtype T = { key: T } or key?: T[]
Use CasesTrees, comments, menus, file systems
TS supportYes, fully supported with type aliases





















------------------------------------------------------------------------------------------------------------

🧱 4. Classes and OOP in TypeScript




1. Public, Private, Protected, and Readonly modifiers?

Ans.

🧾 1. Formal Definition

TypeScript supports access modifiers to control how class members (properties or methods) are accessed:

ModifierVisibilityUsage
publicAccessible from anywhereDefault if no modifier is given
privateAccessible only within the classCompletely hidden outside
protectedAccessible within the class and subclassesNot outside the class hierarchy
readonlyCan be read but not changedWorks with any access level


You use these modifiers in classes to encapsulate logic and enforce access control.

💻 2. Code Example + Explanation

class User { public name: string; private password: string; protected email: string; readonly id: number; constructor(name: string, password: string, email: string, id: number) { this.name = name; this.password = password; this.email = email; this.id = id; } public getEmail(): string { return this.email; // ✅ allowed (protected) } } class Admin extends User { getAdminEmail() { return this.email; // ✅ allowed (protected accessible in subclass) } // changeId(newId: number) { // this.id = newId; // ❌ Error: Cannot assign to 'id' because it is a read-only property // } } const user = new User("Ayan", "secret123", "ayan@example.com", 1); console.log(user.name); // ✅ public // console.log(user.password); // ❌ Error: private // console.log(user.email); // ❌ Error: protected // user.id = 10; // ❌ Error: read-only

🔍 Explanation:

  • name is public, so it's accessible anywhere.

  • password is private, so only accessible inside the User class.

  • email is protected, so accessible inside User and Admin, but not outside.

  • id is readonly, so it can be read but not modified after initialization.


🧃 3. Easy / Slang Explanation

Think of a class like your Instagram account.

ModifierReal-Life Analogy
publicLike your public bio — anyone can see it 🧑‍🤝‍🧑
privateLike your DMs — only you can access them 🔐
protectedLike a private family group — only you + fam 👨‍👩‍👧
readonlyLike your birthdate — visible, but can’t be changed 🎂


So when you say:
private password: string

You're saying:

“Bro, don’t let anyone outside this class touch or even peek at this.”

And when you use:

readonly id: number

It’s like:

“Here’s your ID — you can look at it, but don’t even think about changing it.”


✅ Summary Table

ModifierWho can access it?Can change it?
publicAnyoneYes
privateOnly inside the classYes
protectedClass + subclassesYes
readonlyWherever it’s visible❌ No (after init)







2. Accessors (get / set)?

Ans.

🧾 1. Formal Definition

Accessors in TypeScript are special methods that allow you to read (get) or update (set) the value of a class property. They provide controlled access to private or protected fields while preserving encapsulation.

Syntax:

class Example { private _value: string; get value(): string { return this._value; } set value(newValue: string) { this._value = newValue; } }
  • get: Called when you read the property (obj.value)

  • set: Called when you assign to the property (obj.value = "new")


💻 2. Code Example + Explanation

Example: Bank Account with Balance Control

class BankAccount { private _balance: number = 0; get balance(): number { return this._balance; } set balance(amount: number) { if (amount < 0) { throw new Error("Balance cannot be negative!"); } this._balance = amount; } } const account = new BankAccount(); account.balance = 1000; // Calls the setter console.log(account.balance); // Calls the getter → 1000 // account.balance = -500; // ❌ Throws error: Balance cannot be negative!

🔍 Explanation:

  • _balance is a private field: can't access it directly from outside the class.

  • get balance() allows safe read access.

  • set balance(amount) lets you control how it’s updated, e.g., prevent negatives.

  • You access it like a normal property — but behind the scenes, it calls the getter/setter methods!

Getters and setters make class fields act like smart properties.


🧃 3. Easy / Slang Explanation

Think of get/set like having a bouncer at the door of your data:

  • get: “Yo, you wanna see what’s inside? Sure, but I’ll decide what you see.”

  • set: “Hold up! You can’t just throw anything in here. Let me check it first.”

Real-life analogy:

You're giving your friend access to your fridge, but with rules:

  • ✅ He can look inside (get)

  • 🛑 But if he wants to put something in, like spoiled milk (set), you stop him!

So this:

get balance() { return this._balance }

is like saying:

“Yeah, you can see your bank balance.”

And this:

set balance(amount) { if (amount < 0) throw Error("Nope!") this._balance = amount; }

is like saying:

“No putting negative money in your account, bro.”


✅ Summary Table

FeaturePurposeSyntaxExample
getRead a private fieldget name()console.log(obj.name)
setWrite to a fieldset name(v)obj.name = "Ayan"
BenefitEncapsulation + LogicAdd validation, transform data






3. Abstract Classes?

Ans.

🧾 1. Formal Definition

An abstract class in TypeScript is a class that cannot be instantiated directly.
It serves as a base class for other classes and may include abstract methods (methods without implementation) that must be implemented by subclasses.

  • Use the abstract keyword before the class and method.

  • Think of it like a blueprint — it defines what a subclass should have, but not how.

✅ Example:

abstract class Animal { abstract makeSound(): void; move(): void { console.log("Moving..."); } }

You can’t create new Animal(), but you can extend it.


💻 2. Code Example + Explanation

abstract class Animal { abstract makeSound(): void; // must be implemented in child class move(): void { console.log("Animal is moving..."); } } class Dog extends Animal { makeSound(): void { console.log("Woof! 🐶"); } } const dog = new Dog(); dog.makeSound(); // Woof! dog.move(); // Animal is moving... // const a = new Animal(); ❌ Error: Cannot create an instance of an abstract class.

🔍 Explanation:

  • Animal is abstract, so you can’t do new Animal().

  • It has an abstract method makeSound() → forces subclasses like Dog to implement it.

  • move() is a regular method, so it can be inherited and used as-is.

  • Dog implements makeSound() and inherits move().

Abstract classes let you define structure and behavior, while letting children fill in the blanks.


🧃 3. Easy / Slang Explanation

Imagine an abstract class is like a template or skeleton — it says:

“Yo, every animal has to move and make sound... but I’m not gonna tell you how it sounds. You figure that out, bro.”

So when you write:

abstract class Animal { abstract makeSound(): void; }

It’s like:

“Every animal must know how to scream, bark, roar, or whatever — I don’t care how — just make noise!”

You can’t say:

const a = new Animal(); // ❌

because it’s just a concept. You need to make a specific version like Dog, Cat, or Elephant.

🧠 TL;DR:

  • abstract class = “You can't touch me directly.”

  • abstract method = “Child class better write its own version.”


✅ Summary Table

FeatureAbstract Class
Can instantiate?❌ No
Can extend?✅ Yes
Abstract methods?✅ Must be implemented in child
Regular methods?✅ Can be inherited






4. Implements (Interface in class)?

Ans.

🧾 1. Formal Definition

In TypeScript, the implements keyword is used when a class promises to follow the structure defined by an interface.
The class must then provide concrete implementations for all the properties and methods declared in the interface.

✅ Think of implements as a contract:
“If you implement me, you must follow my rules.”

Syntax:

interface User { name: string; login(): void; } class Admin implements User { name: string; login() { console.log(`${this.name} logged in as Admin.`); } }

💻 2. Code Example + Explanation

Example: Vehicle Interface

interface Vehicle { brand: string; drive(): void; } class Car implements Vehicle { brand: string; constructor(brand: string) { this.brand = brand; } drive(): void { console.log(`${this.brand} is driving...`); } } const myCar = new Car("Tesla"); myCar.drive(); // Tesla is driving...

🔍 Explanation:

  • Vehicle is an interface that defines two things:

    • brand: a string

    • drive(): a method that returns nothing (void)

  • Car is a class that implements Vehicle, meaning:

    • It must have a brand property

    • It must have a drive() method

  • When you create myCar, it behaves exactly like the interface promised.

If the class misses even one property/method from the interface → ❌ TypeScript will throw an error.


🧃 3. Easy / Slang Explanation

Think of an interface like a job posting:

"Yo, I’m looking for someone who has a brand and knows how to drive()."

When your class says implements Vehicle, it’s like replying:

"Bet. I’ve got a brand and I can drive() too. Hire me!"

You can’t fake it — TypeScript is strict:

  • If you're missing anything, it's like showing up to an interview without pants. 🚫

So:

class MyBike implements Vehicle { // if you forget brand or drive(), boom — error }

🧠 The goal: enforce structure and avoid bugs where someone forgets to add an expected method.


✅ Summary Table

ConceptInterfaceClass Implements It
PurposeStructure/ContractFulfills the contract
Method required?✅ Yes✅ Must be implemented
Property check?✅ Yes✅ Must match
Multiple?✅ Yes (comma-separated)implements A, B







5. Static properties and methods?

Ans.

🧾 1. Formal Definition

In TypeScript (and JavaScript), static properties and methods belong to the class itself rather than an instance of the class.

  • You access static members using the class name, not via this or an object.

  • Use the static keyword to define them.

Static members are shared — not unique to each object.

Example:

class MathUtils { static PI = 3.14; static square(num: number): number { return num * num; } }

💻 2. Code Example + Explanation

Example: Utility Class

class Counter { static count = 0; static increment() { Counter.count++; console.log(`Current Count: ${Counter.count}`); } static reset() { Counter.count = 0; console.log("Counter reset."); } } // Accessing static members Counter.increment(); // Current Count: 1 Counter.increment(); // Current Count: 2 Counter.reset(); // Counter reset. // ❌ Error: You can't access static members via an instance const c = new Counter(); // c.increment(); // ❌ Property 'increment' does not exist on type 'Counter'

🔍 Explanation:

  • static count: Shared counter value across all uses of Counter.

  • static increment(): Modifies the static count.

  • You call them like Counter.increment()no need to create an object.

  • Trying to access static members through new Counter() causes an error.


🧃 3. Easy / Slang Explanation

Static is like that one shared group chat — no matter who logs in, they all see the same messages 📱.

So when you write:

static count = 0;

It’s like:

“Hey fam, we all gonna use this one counter. No matter who calls it, the number will go up together.”

And when you do:

Counter.increment();

It's like calling a hotline — you don’t need a personal phone (object), just dial the class name.

🧠 Key Vibes:

  • static = global for the class

  • No this drama

  • Don't need new to use it


✅ Summary Table

FeatureDescription
static property    Belongs to the class, not instances
static method    Can be called without creating an object
Access via  ClassName.method() or ClassName.property
Use Cases    Utilities, counters, config, shared values






6. Generics with classes and functions?

Ans.

🧾 1. Formal Definition

Generics allow you to write flexible, reusable, and type-safe functions and classes by working with types as variables.

  • You use <T> (or any letter) to define a placeholder for a type.

  • Generics are commonly used when the exact type isn’t known up front but will be specified later.

Syntax:

function identity<T>(value: T): T { return value; } class Box<T> { content: T; constructor(content: T) { this.content = content; } }

💻 2. Code Example + Explanation

📦 Generic Function

function identity<T>(value: T): T { return value; } const num = identity<number>(42); // num: number const word = identity<string>("hello"); // word: string

🔍 Explanation:

  • <T> = "I'm a type variable. You decide what T is later."

  • When you call identity<number>(42), you're saying: "T is number."

  • It returns the exact same type you pass in — with full type safety.


🧱 Generic Class

class Storage<T> { private items: T[] = []; addItem(item: T): void { this.items.push(item); } getAll(): T[] { return this.items; } } const numberStorage = new Storage<number>(); numberStorage.addItem(1); numberStorage.addItem(2); // numberStorage.addItem("hello"); ❌ Error const stringStorage = new Storage<string>(); stringStorage.addItem("apple");

🔍 Explanation:

  • Storage<T> is a generic class — it can store any type, but only one type per instance.

  • You create a Storage<number> or Storage<string> as needed.

  • The compiler enforces that only the correct type is used.


😎 3. Easy / Slang Style

Think of generics as "fill-in-the-blank" types:

function identity<T>(value: T): T

It’s like saying:

“Yo, I don’t know what type you’re giving me — could be a number, string, or anything — but I’ll return the same type back. Promise.”


🎒 Generic Function Vibe:

identity<string>("hello") // “I’ll work with strings this time.” identity<number>(42) // “Cool, now I’m a number function.”

📦 Generic Class Vibe:

class Storage<T>

It’s like:

“Hey! I’m a box. I can hold whatever you tell me to — just don’t mix stuff inside.”

If you say Storage<string>, it’s like:

“Okay boss, I’m a box for words only. No numbers allowed in here!”


✅ Summary Table

ConceptDescription
Generic <T>Type placeholder
Function genericfunction<T>(arg: T): T
Class genericclass Box<T> { content: T }
BenefitReusability, type safety, flexibility

Bonus 👀

You can even use multiple types:

function pair<T, U>(a: T, b: U): [T, U] { return [a, b]; } const result = pair<string, number>("score", 100);





7. Generic Constraints?

Ans.

🧾 1. Formal Definition

Generic Constraints in TypeScript allow you to restrict the types that can be used as generic parameters. This ensures that only types with specific properties, methods, or structures are allowed.

You use the extends keyword to define a constraint on a generic type.

✅ Example:

function getLength<T extends { length: number }>(arg: T): number { return arg.length; }

Here, T must be something that has a length property — like a string, array, or custom object with length.


💻 2. Code Example + Explanation

✅ Example 1: Enforcing length property

function getLength<T extends { length: number }>(input: T): number { return input.length; } getLength("Hello"); // ✅ Works: string has length getLength([1, 2, 3]); // ✅ Works: array has length getLength({ length: 10 }); // ✅ Works: object has length // getLength(123); // ❌ Error: number has no 'length'

🔍 Explanation:

  • The generic <T extends { length: number }> ensures that the passed argument has a length property.

  • getLength(123) gives an error because numbers don’t have length.


✅ Example 2: Constraint with interface

interface Person { name: string; } function greet<T extends Person>(person: T): string { return `Hello, ${person.name}`; } greet({ name: "Ayan" }); // ✅ OK greet({ name: "Haider", age: 22 }); // ✅ OK (extra props allowed) greet({ age: 22 }); // ❌ Error: 'name' is missing

🔍 Explanation:

  • The generic T must at least match the shape of Person.

  • It allows extra properties, but not missing required ones.


🧃 3. Easy / Slang Explanation

Alright, here’s the chill breakdown:

Generics are like:

“Yo, I’ll take any type you give me…”

But sometimes, you wanna say:

“...but only if it has this thing!”

So you slap on:

<T extends Something>

It’s like saying:

“Yeah, I’ll take you, as long as you’ve got these credentials.”
(Like a VIP pass 🎟️)

🔥 Slang Example:

function showName<T extends { name: string }>(thing: T) { console.log(thing.name); }

You’re telling TS:

“Bro, whatever you give me must at least have a name... I don’t care what else it has.”


✅ Summary Table

ConceptDescription
extends in generics    Restricts what type can be passed in
Structural typing    Type must at least have the required shape
Use Case    Enforcing presence of props, methods, or matching interfaces
Error on invalid    Compile-time errors for missing required properties














----------------------------------------------------------------------------------------------

🧰 5. Generics (Important for Interviews!)




1. Basics: <T>?

Ans.

🧾 1. Formal Definition

In TypeScript, <T> is a generic type parameter used to represent any type. It acts like a placeholder that will be replaced with a real type when the function, class, or interface is used.

This allows you to write reusable, type-safe code that works with many types without losing type information.

✅ Example:

function identity<T>(value: T): T { return value; }

Here, T can be any type: string, number, boolean, custom objects, etc.


💻 2. Code Example + Explanation

✅ Basic Generic Function

function identity<T>(value: T): T { return value; } const a = identity<string>("Hello"); // a: string const b = identity<number>(100); // b: number

🔍 Explanation:

  • <T> = “Hey, I’m a type variable.”

  • You pass the actual type (string, number) when calling the function.

  • identity just returns the input — but strongly typed!

  • If you pass a string, it knows it's a string — if you pass a number, it knows that too.

✅ This gives:

  • Type safety (no wrong types sneak in)

  • Reusability (works with any type)


✅ Same Function Without Generics (Bad Way)

function identityBad(value: any): any { return value; }

With any, TypeScript gives up and doesn't help you with autocomplete or type checking. You lose the benefits of TypeScript's type system.


😎 3. Easy / Slang Explanation

Alright, here’s the chill version:

Think of <T> as a blank that you fill in later.

function identity<T>(value: T): T {

It’s saying:

“Yo, give me something, I don’t care what it is... but whatever it is, I’ll make sure I treat it exactly like that.”

If you give it a string, it’s a string.
If you give it a number, it’s a number.
If you give it a cat, it’s a cat 😸

So instead of writing separate versions like:

function numberIdentity(n: number): number {} function stringIdentity(s: string): string {}

You just write one function using <T>, and TypeScript figures out the rest.


✅ Summary

TermMeaning
<T>Generic type placeholder
T in useBecomes the real type when used
PurposeReusability + Type Safety






2. Generic Constraints (extends)?

Ans.

🧾 1. Formal Definition

In TypeScript, Generic Constraints allow you to limit or restrict what types can be used in a generic by using the extends keyword.

This ensures that the type passed must have certain properties or meet certain conditions. Without constraints, a generic accepts any type.

✅ Example:

function logLength<T extends { length: number }>(arg: T): number { return arg.length; }

Here, the generic T is constrained — it must be a type that has a length property.


💻 2. Code Example + Explanation

✅ Example 1: length constraint

function getLength<T extends { length: number }>(input: T): number { return input.length; } getLength("hello"); // ✅ string has length getLength([1, 2, 3]); // ✅ array has length getLength({ length: 5 }); // ✅ object with length // getLength(123); // ❌ Error: number has no 'length'

🔍 Explanation:

  • T extends { length: number }: means T must have a length property.

  • getLength(123) gives an error because numbers don’t have a length.

  • Works with strings, arrays, and any object with a length.


✅ Example 2: Constrain to Interface

interface Person { name: string; } function greet<T extends Person>(person: T): string { return `Hello, ${person.name}`; } greet({ name: "Ayan" }); // ✅ Works greet({ name: "Haider", age: 22 }); // ✅ Works // greet({ age: 22 }); // ❌ Error: 'name' is missing

🔍 Explanation:

  • T extends Person means T must have at least the shape of Person.

  • Extra properties like age are allowed.

  • Missing name → error!


😎 3. Easy / Slang Explanation

Let’s keep it real:

<T> is like:

“I’ll take anything you give me.”

But sometimes you wanna say:

“I’ll only take it if it has certain stuff.”

That’s where extends comes in. It’s like giving your generic a minimum requirement.

Think of it like:

function doSomething<T extends CoolGuy>(value: T)

You're saying:

"You can pass me any dude, as long as he’s cool (aka has the stuff CoolGuy has)."

It’s like putting up a sign at the door:

“🎟️ Only people with VIP pass (aka certain props) allowed.”

So TypeScript won't let you pass garbage. It keeps you safe.


✅ Summary

FeatureDescription
extends in genericAdds a constraint to the type
BenefitBetter type safety, more control
Common useEnforce structure or required properties







3. Generic Functions?

Ans.

🧾 1. Formal Definition

A Generic Function in TypeScript is a function that is written with a generic type parameter — typically denoted as <T> — which allows it to operate on multiple types while retaining type safety.

This helps write reusable and flexible functions without sacrificing the benefits of strong typing.

✅ Syntax:

function functionName<T>(param: T): T { return param; }

Here, T is a placeholder for a type that will be passed in later when the function is used.


💻 2. Code Example + Explanation

✅ Example 1: Simple Identity Function

function identity<T>(value: T): T { return value; } const str = identity<string>("Hello"); const num = identity<number>(123);

🔍 Explanation:

  • identity<T>: defines a generic function with type parameter T.

  • value: T: parameter must be of type T.

  • : T: return type is also T.

  • When calling identity<string>("Hello"), T is inferred as string.

  • Same logic for number, boolean, objects, etc.


✅ Example 2: Generic Function with Arrays

function firstElement<T>(arr: T[]): T { return arr[0]; } const firstNum = firstElement([1, 2, 3]); // number const firstStr = firstElement(["a", "b", "c"]); // string

🔍 Explanation:

  • T[] = array of some unknown type T.

  • Returns the first element, and preserves its type.

  • So you get proper autocomplete & safety.


✅ Example 3: Function with Multiple Generics

function merge<T, U>(a: T, b: U): [T, U] { return [a, b]; } const result = merge<string, number>("age", 25); // ['age', 25]

🔍 Explanation:

  • Two generics: <T, U>

  • Return type is a tuple: [T, U]

  • Super useful in combining values of different types safely.


😎 3. Easy / Slang Explanation

Okay, here's the chill version of it:

Think of Generic Functions like a template. You're saying:

“I don’t care what type you give me — just tell me what it is, and I’ll work with it properly.”

Like a waiter who can handle:

  • 🍕 Pizza

  • 🍔 Burger

  • 🍜 Noodles
    …without messing up your order.

If you don’t use generics, it’s like saying:

“Give me anything” (any)
But if you do use generics, it’s more like:
“Tell me what it is, and I’ll treat it exactly like that.”

So:

function identity<T>(value: T): T {

It’s saying:

“You give me a type, and I promise to stick with it.”


✅ Summary

FeatureDescription
<T>Generic type placeholder
Generic FunctionsFunctions that work with any (typed) input
BenefitsType safety + code reusability






4. Generic Interfaces and Classes?

Ans.

🧾 1. Formal Definition

Generic Interfaces and Classes allow you to create flexible, reusable types and blueprints that can work with any type, specified later.

  • You define generics with <T> (or other letters) just like with functions.

  • This enables strongly-typed data structures and contracts that adapt to different types.


💻 2. Code Example + Explanation

✅ Generic Interface

interface Box<T> { content: T; getContent(): T; } const numberBox: Box<number> = { content: 123, getContent() { return this.content; } }; const stringBox: Box<string> = { content: "Hello", getContent() { return this.content; } };

🔍 Explanation:

  • Box<T> is a generic interface with a property and method both using T.

  • numberBox uses Box<number>, so content must be a number.

  • stringBox uses Box<string>, so content must be a string.


✅ Generic Class

class Storage<T> { private items: T[] = []; addItem(item: T): void { this.items.push(item); } getItems(): T[] { return this.items; } } const numberStorage = new Storage<number>(); numberStorage.addItem(10); numberStorage.addItem(20); // numberStorage.addItem("hello"); // ❌ Error: string not allowed const stringStorage = new Storage<string>(); stringStorage.addItem("apple");

🔍 Explanation:

  • Storage<T> is a generic class that stores items of type T.

  • You create instances specifying the type: Storage<number> or Storage<string>.

  • TypeScript ensures you only add items of the right type.


😎 3. Easy / Slang Explanation

Generics in interfaces and classes are like making a template or blueprint that says:

“Hey, I’ll hold or work with whatever type you tell me — a box for apples, or a box for toys, you decide!”

So,

  • interface Box<T> = “I’m a box, I don’t care what you put in me, but I promise to treat it consistently.”

  • class Storage<T> = “I’m a container. When you create me, you say what I’ll hold, and I won’t accept anything else.”


✅ Summary Table

FeatureGeneric InterfaceGeneric Class
PurposeDefine flexible object shapesDefine flexible object blueprints
Syntaxinterface Name<T> {}class Name<T> {}
Type SafetyEnsures members use generic type TEnsures properties and methods use T
UsageFor typing objectsFor creating reusable data structures







5. keyof T, T[K]?

Ans.

🧾 1. Formal Definition

  • keyof T is a TypeScript operator that produces a union of the keys (property names) of type T as string literal types.

  • T[K] is called indexed access type, which means the type of the property K in type T. Here, K must be a key of T.

Put simply:

  • keyof T gives you all possible keys of T.

  • T[K] gives you the type of the value stored at key K in T.


💻 2. Code Example + Explanation

interface Person { name: string; age: number; location: string; } // keyof T example type PersonKeys = keyof Person; // PersonKeys = "name" | "age" | "location" // T[K] example type NameType = Person["name"]; // string type AgeType = Person["age"]; // number // Using keyof and T[K] together function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const person: Person = { name: "Ayan", age: 25, location: "Poland" }; const personName = getProperty(person, "name"); // type: string, value: "Ayan" const personAge = getProperty(person, "age"); // type: number, value: 25

🔍 Explanation:

  • keyof Person creates a union type of all keys: "name" | "age" | "location".

  • Person["name"] accesses the type of the name property, which is string.

  • The function getProperty takes an object obj and a key key that must be one of obj’s keys.

  • It returns the value of that property with the correct type.


😎 3. Easy / Slang Explanation

Think of an object as a box with labels:

const person = { name: "Ayan", age: 25, location: "Poland" };
  • keyof T = "all the labels on the box"
    "name", "age", "location" in this case.

  • T[K] = "what’s inside the box at this label"
    → If the label is "name", the content type is string.

In the function:

function getProperty(obj, key) { return obj[key]; }

We say:

“Give me a box and one of its labels, I’ll give you the stuff inside — and I’ll tell you the type exactly.”


✅ Summary Table

OperatorMeaningExample
keyof TUnion of keys of type T        `"name"
T[K]Type of property K in type T    Person["name"]string







6. Nested Generics, Conditional Types with Generics?

Ans.

🧾 1. Formal Definition

Nested Generics

Nested Generics happen when a generic type uses another generic type as its parameter. This enables creating complex, reusable type constructs that work with multiple layers of data types.

Example: Array<Promise<string>> — an array of promises of strings.

Conditional Types with Generics

Conditional Types let you express types that depend on conditions, often involving generic type parameters. They have this form:

T extends U ? X : Y

Meaning: If type T can be assigned to type U, then the type resolves to X; otherwise, it resolves to Y.

When combined with generics, conditional types enable powerful type transformations and checks.


💻 2. Code Example + Explanation

Nested Generics Example

// A generic wrapper type type ApiResponse<T> = { data: T; status: number; }; // Nested generics: ApiResponse holding an array of strings const response: ApiResponse<Array<string>> = { data: ["apple", "banana", "cherry"], status: 200, };

🔍 Explanation:

  • ApiResponse<T> is generic.

  • We pass Array<string> as the type parameter, so data becomes an array of strings.

  • This is a nested generic: generic inside a generic.


Conditional Types with Generics Example

// A conditional type that extracts the return type if T is a function, otherwise T itself type ReturnTypeOrSelf<T> = T extends (...args: any[]) => infer R ? R : T; // Test cases type A = ReturnTypeOrSelf<() => number>; // number type B = ReturnTypeOrSelf<string>; // string

🔍 Explanation:

  • T extends (...args: any[]) => infer R checks if T is a function type.

  • If yes, ReturnTypeOrSelf<T> resolves to the return type R of the function.

  • Otherwise, it resolves to T itself.

  • infer R is a special keyword to extract (infer) a type inside a conditional type.


😎 3. Easy / Slang Explanation

Nested Generics

Think of generics like Russian dolls — you can stack them inside each other.

  • ApiResponse<T> = “I hold something.”

  • If you say ApiResponse<Array<string>>, you’re saying “I hold an array of strings.”

So nested generics are just generics wrapped in other generics — like putting a box inside a bigger box.


Conditional Types with Generics

Conditional types are like if-else for types:

type ReturnTypeOrSelf<T> = T extends (...args: any[]) => infer R ? R : T;

It says:

“If you’re a function, I wanna know what you return. If not, I’ll just keep you as you are.”

It’s like asking a buddy:

“Hey, if you’re a pizza delivery guy, what’s your specialty pizza? If you’re not, what do you do?”


✅ Summary Table

ConceptDescriptionExample
Nested GenericsUsing generics inside other genericsApiResponse<Array<string>>
Conditional TypesType logic with if-else style using genericsT extends U ? X : Y
infer keywordExtract a type from another typeinfer R extracts function return


















-------------------------------------------------------------------------------------------

🛠️ 6. Working with Modules & Namespaces



1. ES Module syntax in TS?

Ans.

🧾 1. Formal Definition

ES Modules (ECMAScript Modules) are the standard module system in JavaScript and TypeScript for organizing and sharing code across files.

In TypeScript, you use import and export statements to bring in or expose variables, functions, classes, types, etc. This is also fully compatible with modern JavaScript.

TypeScript supports two main ways of exporting:

  • Named Exports (export const, export function, etc.)

  • Default Export (export default ...)


💻 2. Code Example + Explanation

✅ Named Exports Example

// mathUtils.ts
export const add = (a: number, b: number): number => a + b; export const multiply = (a: number, b: number): number => a * b; // main.ts import { add, multiply } from "./mathUtils"; console.log(add(5, 3)); // 8 console.log(multiply(5, 3)); // 15

🔍 Explanation:

  • mathUtils.ts exports multiple functions using export.

  • main.ts imports them using destructured { } syntax.


✅ Default Export Example

// greeting.ts
export default function greet(name: string) { return `Hello, ${name}!`; } // main.ts import greet from "./greeting"; console.log(greet("Ayan")); // Hello, Ayan!

🔍 Explanation:

  • greeting.ts has a default export, so no {} needed while importing.

  • You can name the import anything (e.g., greet, hello, etc.).


✅ Mixed Exports

// user.ts
export const role = "admin"; export default class User { constructor(public name: string) {} } // main.ts import User, { role } from "./user"; const u = new User("Ayan"); console.log(u.name); // Ayan console.log(role); // admin

😎 3. Easy / Slang Explanation

Alright, imagine your TypeScript files like pizza boxes 🍕:

  • export = “Here, I’m sharing this slice with others.”

  • import = “Yo, I’ll take that slice from your box.”


🧀 Named Export = You pick the toppings

export const cheese = "Mozzarella";
export const crust = "Thin";

You choose exactly what you want:

import { cheese } from "./pizza"; // just the cheese!

🍕 Default Export = You get the whole pizza

export default "Whole Pizza";

You can call it anything:

import Pizza from "./pizza";

🧪 TypeScript-Specific Notes:

  • TypeScript uses ESM syntax by default if your tsconfig.json has "module": "ESNext" or "ES6".

  • Works seamlessly with modern tooling like Vite, Webpack, and Next.js.


✅ Summary Table

SyntaxPurposeExample
export const foo = ...Named exportimport { foo } from "./file"
export default somethingDefault exportimport anything from "./file"
import { x } from ...Import specific itemsimport { x } from "./mod"
import x from ...Import default exportimport x from "./mod"
import x, { y } from ...Both default and named importsimport x, { y } from "./mod"







2. import, export, default?

Ans.

🧾 1. Formal Definition

export

The export keyword in TypeScript (and JavaScript) is used to expose variables, functions, classes, or types from a module (file) so that they can be used in other files.

There are two kinds:

  • Named Export: You explicitly name what you're exporting.

  • Default Export: You mark a single value as the "main" thing to export from a module.

import

The import keyword brings code from one module into another. You can import:

  • Specific items using { }

  • A default export without { }

  • Both at once


💻 2. Code Example + Explanation

✅ Named Export & Import

// math.ts
export const add = (a: number, b: number) => a + b; export const subtract = (a: number, b: number) => a - b; // main.ts import { add, subtract } from './math'; console.log(add(2, 3)); // 5 console.log(subtract(5, 2)); // 3

🧠 Explanation:

  • export shares add and subtract from math.ts.

  • In main.ts, we import them using { }.


✅ Default Export & Import

// greet.ts
export default function greet(name: string) { return `Hello, ${name}`; } // main.ts import greet from './greet'; console.log(greet('Ayan')); // Hello, Ayan

🧠 Explanation:

  • export default marks greet as the primary export.

  • You import it without curly braces.


✅ Mixed Export

// user.ts
export const role = "admin"; export default class User { constructor(public name: string) {} } // main.ts import User, { role } from './user'; const u = new User('Ayan'); console.log(u.name); // Ayan console.log(role); // admin

🧠 Explanation:

  • You can have one default export and many named exports in the same file.

  • Import both using the default name + { } for named ones.


😎 3. Easy / Slang Style

Imagine each file is a toolbox 🧰.

🧱 export = “I’m putting this tool out for others.”

  • Named export: "Here are a few tools. Take whichever you need."

    export const hammer = "🔨";
    export const wrench = "🔧";
  • Default export: "This is the main tool."

    export default "🛠️ All-in-one tool";

📦 import = “I’m grabbing tools from another toolbox.”

  • Grab named tools:

    import { hammer } from "./tools";
  • Grab the main (default) tool:

    import Tool from "./tools";
  • Grab both:

    import Tool, { hammer } from "./tools";

✅ Summary Table

SyntaxPurposeExample
export const x = ...Named exportimport { x } from "./file"
export default ...Default exportimport x from "./file"
import { x } from ...Named importimport { x } from "./file"
import x from ...Default importimport something from "./file"
import x, { y } from ...Default + named importimport def, { named } from "./file"







3. Type-only imports (import type)?

Ans.

🧾 1. Formal Definition

In TypeScript, import type is used to only import types, not values. It tells the compiler this import will never exist at runtime — it's just for type checking.

This is helpful for:

  • Preventing unnecessary code from being bundled.

  • Avoiding circular dependencies.

  • Improving performance in type-only contexts (especially in large codebases).

Syntax:

import type { TypeName } from './module';

This was introduced in TypeScript 3.8.


💻 2. Code Example + Explanation

✅ Using import type

// types.ts export type User = { id: number; name: string; }; // main.ts import type { User } from './types'; function printUser(user: User) { console.log(user.name); }

🧠 Explanation:

  • User is only used for type-checking, not as a real value.

  • import type avoids pulling any runtime JavaScript code.

  • If types.ts only exports types, this keeps the compiled JavaScript clean (no require() or import statements).


⚠ Without import type (regular import)

import { User } from './types';

Even if you're just using User as a type, this might cause unnecessary runtime imports, especially when using bundlers like Webpack, Rollup, or esbuild.


✅ Mixing Type and Value Imports (using import + import type)

// utils.ts export type ID = number; export const getId = () => 42; // main.ts import { getId } from './utils'; import type { ID } from './utils'; const id: ID = getId();

Here, getId is a real function you use at runtime, and ID is a type used only for type-checking.


😎 3. Easy / Slang Style

🧢 Think of import type as a ghost import 👻

It exists only in the TypeScript world, not in real life (runtime JavaScript). It's like saying:

“Hey TS, I need this type so I don’t mess up — but I don’t actually need the thing in my code when it runs.”


🎯 Why use it?

  • 🚀 Speeds up compilation

  • 🧼 Keeps JS output clean

  • 🔁 Helps avoid weird bugs caused by circular imports


🧠 Mental Model:

  • import type: “Only during the planning stage.”

  • Regular import: “I’ll actually need this during the performance.”


✅ Summary

FeatureUsePulls into runtime?
import type {...}    Just for type-checking    ❌ No
import {...}    For runtime values & types    ✅ Yes








4. Declaration Merging?

Ans.

🧾 1. Formal Definition

Declaration Merging is a unique TypeScript feature where the compiler automatically merges multiple declarations of the same name into a single definition.

It usually applies to:

  • Interfaces

  • Namespaces

  • Functions + namespaces

  • Classes + namespaces

  • Enums + namespaces

Only works if the declarations have the same name and are in the same scope.


💻 2. Code Example + Explanation

🧩 Example 1: Interface Merging

interface User { name: string; } interface User { age: number; } const u: User = { name: "Ayan", age: 23 };

🧠 Explanation:

  • Both interfaces named User are merged into one.

  • Final User interface: { name: string; age: number }

  • TypeScript combines their properties as if it was written in one block.


🧩 Example 2: Function + Namespace Merging

function greet(name: string) { return `Hello, ${name}`; } namespace greet { export const time = "morning"; } console.log(greet("Ayan")); // Hello, Ayan console.log(greet.time); // morning

🧠 Explanation:

  • Function and namespace share the same name.

  • The namespace adds a property time onto the function object.

  • Now greet behaves both like a function and an object with extra properties.


🧩 Example 3: Enum + Namespace Merging

enum Status { Active, Inactive } namespace Status { export function toString(s: Status) { return s === Status.Active ? "Active" : "Inactive"; } } console.log(Status.toString(Status.Active)); // Active

🧠 Explanation:

  • namespace Status merges into the enum Status.

  • Adds a utility function toString.


😎 3. Easy / Slang Style

👯 “Same name? Let’s team up!”

Declaration merging is like two or more pieces of code joining forces if they have the same name and play in the same sandbox.


🧠 Think of it like:

  • You make a character profile for a game:

    interface Player { name: string; } interface Player { level: number; }

    Now Player has both name and level. Boom! 🎮


🔧 Why it’s cool?

  • You can extend things without modifying original code.

  • Useful in third-party libraries, plugins, and type augmentation.


⚠️ But be careful:

  • If two interfaces have the same property name, the last one overrides it.

  • Works mostly with interfaces, not type aliases!


✅ Summary Table

TypeMerges?Notes
interfaceProperties combine
namespaceCan merge with functions, enums, etc.
type aliasCannot be merged
class + namespaceAdd static utilities to classes







5. Type Declaration Files (.d.ts)?

Ans.

🧾 1. Formal Definition

Type Declaration Files (with the .d.ts extension) are files that only contain type information for JavaScript code.

These files tell TypeScript:

  • What types, interfaces, or structures exist in a JavaScript module or library.

  • How to understand code that doesn’t have TypeScript types.

They are like type manuals for JS code — they don’t have any logic or runtime code, just type definitions.

You’ll often see them when:

  • Using plain JavaScript libraries in TypeScript (lodash, express, etc.)

  • Writing a TypeScript library that others can import with types.

  • Declaring global types, modules, or external interfaces.

Example:

// my-lib.d.ts declare module "my-lib" { export function greet(name: string): string; }

💻 2. Code Example + Explanation

🧩 Example 1: Declaring a global variable

// globals.d.ts declare const VERSION: string;

Then in your code:

console.log("App Version:", VERSION);

✅ Now TypeScript knows that VERSION is a string defined globally.


🧩 Example 2: Declaring types for a JS library

Suppose you use a JS file math-lib.js:

// math-lib.js function add(a, b) { return a + b; } module.exports = { add };

You can write a .d.ts file:

// math-lib.d.ts declare module "math-lib" { export function add(a: number, b: number): number; }

Then in your TS file:

import { add } from "math-lib"; console.log(add(2, 3)); // ✅ TS knows the function signature

🧩 Example 3: Adding types to window or globalThis

// globals.d.ts interface Window { myCustomMethod: () => void; }

In code:

window.myCustomMethod = () => { console.log("This works!"); };

😎 3. Easy / Slang Explanation

💬 Think of .d.ts files like “cheat sheets” for TypeScript 📝

They don’t run, they just describe:

“Hey TS, here’s what this JavaScript thing looks like — just trust me!”


🎯 Why it’s useful?

  • JS libraries don’t have types — .d.ts files fill the gap 🧩

  • You can teach TS about globals, modules, or libraries that didn’t come with type info.

  • Keeps your TS code safe and autocomplete-friendly


🧠 Real-world analogy:

You’re playing a game (TS), but someone made custom content (JS library). The .d.ts file is like a guidebook so you don’t break the rules and know how to play with the new content.


✅ Summary

FeatureDescription
.d.ts file    Type-only file, no runtime code
Purpose    Describe JS code structure to TS
Use cases    JS libraries, global variables, modules
Compiled to JS?    ❌ Never — it's stripped at build time






6. Ambient Declarations (declare)?

Ans.

🧾 1. Formal Definition

In TypeScript, Ambient Declarations are used to tell the compiler about variables, types, modules, or functions that are defined elsewhere — typically outside your TypeScript project (like in plain JavaScript or globally available by a script).

We use the declare keyword to make these declarations. They don’t produce any JavaScript code; they only help TypeScript understand the shape of external code.

Think of declare as saying:
🗣 “Hey TypeScript, trust me — this thing exists somewhere, just assume it’s real!”

✅ Syntax example:

declare let GLOBAL_VAR: number;

💻 2. Code Examples + Explanation


🧩 Example 1: Declaring a global variable

// globals.d.ts declare const VERSION: string;

In your TypeScript file:

console.log(VERSION); // No error if used after declaring

Explanation:

  • We're telling TypeScript that there is a constant VERSION, and it will be defined somewhere at runtime (e.g., injected by a script).

  • TypeScript won't complain, even though it doesn't see the actual value.


🧩 Example 2: Declaring a global function

// in an ambient declaration file or at the top level declare function logUserActivity(activity: string): void;

Then you can use it:

logUserActivity("User logged in");

Even if the function is defined in a separate JS file, TypeScript won’t throw an error — thanks to declare.


🧩 Example 3: Declaring a module

// my-lib.d.ts declare module "fancy-logger" { export function log(message: string): void; export const version: string; }

Then use it in TS:

import { log, version } from "fancy-logger"; log("Hey there!"); console.log(version);

Explanation:

  • declare module tells TypeScript: “This module exists, and here’s what’s inside.”


😎 3. Easy / Slang Explanation

🧠 Think of declare like a TypeScript pinky promise 🩷

You're saying:

“I SWEAR this thing exists — just don’t worry about where it came from.”

But:

  • You don’t define the value.

  • You just describe it so TS doesn’t complain.


🎯 Real-life analogy:

You're writing code that talks to a JS file from another developer, or a value that’s injected into the browser like window, document, or third-party scripts.

TypeScript: "I don't see it in your code!"
You: "declare"
TypeScript: "Aight, I trust you."


✅ Summary

declare Use CasePurpose
declare var/let/const    Declare global variables
declare function    Declare global functions
declare module    Describe external JS modules
declare namespace    Define global objects with nested members
declare class    Describe a class structure without defining it


















----------------------------------------------------------------------------------------------

🌐 7. TypeScript with Frontend Frameworks




1. TypeScript with React (TSX): Props, State, Refs, Context API, Custom Hooks?

Ans.

1️⃣ Props

🧾 Formal Definition:

Props in React are inputs passed to a component from its parent. In TypeScript, we use interfaces or type aliases to define the shape of props to ensure type safety and autocompletion.

type GreetingProps = { name: string; };

💻 Code Example:

type GreetingProps = { name: string; }; const Greeting: React.FC<GreetingProps> = ({ name }) => { return <h1>Hello, {name}!</h1>; };

Explanation:

  • GreetingProps defines that the component expects a name prop of type string.

  • React.FC (or React.FunctionComponent) lets us type the function with props.


😎 Easy Explanation:

Props are like function arguments for components.
Using TypeScript just makes sure you're not sending a banana 🍌 to a function expecting an apple 🍎.


2️⃣ State

🧾 Formal Definition:

React state holds data that changes over time. With TypeScript, you explicitly define the type of the state to avoid unexpected bugs.


💻 Code Example:

import { useState } from "react"; const Counter = () => { const [count, setCount] = useState<number>(0); return <button onClick={() => setCount(count + 1)}>Count: {count}</button>; };

Explanation:

  • useState<number>(0) means this state will always be a number.

  • Helps prevent you from accidentally passing a string like "2" to setCount.


😎 Easy Explanation:

State = memory for your component 🔢
TypeScript makes sure you don’t shove the wrong thing in that memory slot.


3️⃣ Refs

🧾 Formal Definition:

Refs are used to access DOM elements or hold mutable values. TypeScript helps us correctly type the reference, especially when accessing DOM nodes.


💻 Code Example:

import { useRef, useEffect } from "react";
const InputFocus = () => { const inputRef = useRef<HTMLInputElement>(null); useEffect(() => { inputRef.current?.focus(); }, []); return <input ref={inputRef} placeholder="Focus me!" />; };

Explanation:

  • useRef<HTMLInputElement>(null) tells TS this ref is for an <input>.

  • inputRef.current?.focus() safely focuses it if it’s not null.


😎 Easy Explanation:

Refs are like grabbing something directly with your hand 👋
TS makes sure you’re grabbing the right thing (like an input, not a button).


4️⃣ Context API

🧾 Formal Definition:

The Context API lets you share values across components without prop drilling. With TS, you define types for both context value and provider props.


💻 Code Example:

import React, { createContext, useContext, useState } from "react";
type Theme = "light" | "dark"; const ThemeContext = createContext<Theme>("light"); const ThemeProvider = ({ children }: { children: React.ReactNode }) => { const [theme, setTheme] = useState<Theme>("light"); return ( <ThemeContext.Provider value={theme}> {children} </ThemeContext.Provider> ); }; const ThemeDisplay = () => { const theme = useContext(ThemeContext); return <div>Current theme: {theme}</div>; };

Explanation:

  • We create a context of type Theme.

  • useContext gives access to the typed value — "light" or "dark" only.


😎 Easy Explanation:

Context is like a global settings box 📦
TS makes sure everyone gets the right kind of setting — no undefined surprises.


5️⃣ Custom Hooks

🧾 Formal Definition:

Custom hooks are functions that start with use and encapsulate reusable logic. TypeScript allows us to specify argument and return types clearly.


💻 Code Example:

import { useState } from "react";
const useToggle = (initial: boolean): [boolean, () => void] => { const [value, setValue] = useState(initial); const toggle = () => setValue((prev) => !prev); return [value, toggle]; }; const ToggleComponent = () => { const [on, toggle] = useToggle(false); return <button onClick={toggle}>{on ? "ON" : "OFF"}</button>; };

Explanation:

  • useToggle returns a tuple: [boolean, () => void].

  • The hook logic is type-safe and reusable.


😎 Easy Explanation:

Custom hooks = mini superpowers 🦸‍♀️
TS makes sure you return exactly what you promised, no surprise villains (errors).


✅ Summary Table

FeaturePurposeHow TypeScript Helps
PropsPass data into componentsDefines the shape of props clearly
StateInternal component dataPrevents type mismatch in useState
RefsAccess DOM nodes or store mutable valuesEnsures correct ref type (e.g., HTMLDivElement)
Context APIGlobal-like shared dataEnforces strict type of context values
Custom HooksReusable logicStrong typing of args & return values








2. TypeScript with Next.js?

Ans.

🚀 TypeScript with Next.js


🧾 1. Formal Definition:

Next.js is a full-stack React framework that supports TypeScript out of the box. When you use TypeScript in a Next.js project, it provides:

  • Strong typing for pages, props, API routes

  • Autocompletion and better dev experience

  • Compile-time safety for file-based routing

When you run Next.js with TS, it auto-generates a tsconfig.json file and sets up necessary types under the hood.

✅ Example:

npx create-next-app@latest my-app --typescript

💻 2. Code Examples + Explanation:

📄 2.1. Pages with Typed Props (SSR)

// pages/index.tsx type HomeProps = { name: string; }; export default function Home({ name }: HomeProps) { return <h1>Hello, {name}</h1>; } export async function getServerSideProps() { return { props: { name: "Ayan", }, }; }

Explanation:

  • HomeProps ensures only valid props are passed to the page component.

  • Next.js injects props via getServerSideProps.


📦 2.2. API Routes with Types

// pages/api/hello.ts import type { NextApiRequest, NextApiResponse } from "next"; export default function handler( req: NextApiRequest, res: NextApiResponse<{ message: string }> ) { res.status(200).json({ message: "Hello API" }); }

Explanation:

  • NextApiRequest and NextApiResponse give full typing support for query, body, status, etc.

  • Helps avoid bugs like missing keys in request bodies.


🌍 2.3. Custom App & Document with TS

// pages/_app.tsx import type { AppProps } from "next/app"; function MyApp({ Component, pageProps }: AppProps) { return <Component {...pageProps} />; } export default MyApp;

Explanation:

  • AppProps types Component and pageProps correctly for all pages.


🧭 2.4. Typed useRouter (Routing)

import { useRouter } from "next/router"; const Post = () => { const router = useRouter(); const { id } = router.query; // id is `string | string[] | undefined` return <div>Post ID: {id}</div>; };

Pro Tip: To narrow id to a string, check type guards:

if (typeof id === "string") { console.log(id.toUpperCase()); }

🎯 2.5. Dynamic Routes + Static Props

// pages/blog/[slug].tsx type BlogProps = { slug: string; }; export default function Blog({ slug }: BlogProps) { return <h1>Blog: {slug}</h1>; } export async function getStaticProps({ params }: { params: { slug: string } }) { return { props: { slug: params.slug, }, }; } export async function getStaticPaths() { return { paths: [{ params: { slug: "first-post" } }], fallback: false, }; }

😎 3. Easy/Slang Explanation:

Think of Next.js and TypeScript like peanut butter & jelly 🥪
Next.js brings the power, and TypeScript keeps it safe.

  • Props? You type them.

  • API routes? Typed requests & responses.

  • Routing? No guessing, types tell you what’s allowed.

  • Static & server-side props? TS guards your data like a bouncer at a club 🚷

You don’t gotta worry about "what’s coming in or going out" — TypeScript checks that for you.


✅ Summary Cheatsheet

FeatureTypeScript Usage Example
Create App with TS    npx create-next-app --typescript
Page Props Typing    type Props = {} + getStaticProps/SSR
API Route Typing    NextApiRequest, NextApiResponse
Global App Typing    AppProps in _app.tsx
Dynamic Routes       Typed params: { slug: string }
Router Query    typeof id === "string" check for safety







3. TypeScript with Node.js / Express?

Ans.

🧾 1. Formal Definition

TypeScript can be used in Node.js with Express to bring static type checking, IntelliSense, and better developer productivity. With it, you can write more robust APIs, catch errors at compile time, and define custom types for request and response objects.

Installing TypeScript into an Express project involves:

  • Initializing TypeScript config

  • Using ts-node or building with tsc

  • Typing Express handlers and middlewares

📦 Common types used:

  • Request, Response, NextFunction from express

  • RequestHandler, Application, etc.


💻 2. Code Example + Explanation

📁 Project Setup

npm init -y npm install express npm install -D typescript ts-node @types/node @types/express npx tsc --init

Edit tsconfig.json:

{ "compilerOptions": { "target": "ES2020", "module": "CommonJS", "outDir": "dist", "rootDir": "src", "esModuleInterop": true, "strict": true } }

📝 Basic Express Server (with types)

// src/index.ts import express, { Request, Response, NextFunction } from "express"; const app = express(); app.use(express.json()); app.get("/", (req: Request, res: Response) => { res.send("Hello TypeScript with Express!"); }); app.post("/user", (req: Request, res: Response) => { const { name }: { name: string } = req.body; res.json({ message: `User ${name} created.` }); }); app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).json({ error: err.message }); }); app.listen(3000, () => console.log("Server running on port 3000"));

🧠 Explanation:

  • Request, Response, NextFunction are typed from express.

  • req.body is destructured and explicitly typed.

  • ✅ Middleware errors are typed with Error.

  • 🛡️ Type safety ensures you don’t mistype body fields or methods.


📦 Example: Custom Type for Request Body

type User = { id: number; name: string; }; app.post("/user", (req: Request<{}, {}, User>, res: Response) => { const user = req.body; res.status(201).json({ message: `User ${user.name} created` }); });

This line:

Request<Params, ResBody, ReqBody, Query>

means you can type:

  • URL params

  • Response body

  • Request body

  • Query string


⚙ Run the App

npx ts-node src/index.ts

Or compile first:

npx tsc node dist/index.js

😎 3. Easy / Slang Explanation

TypeScript in Express is like having an assistant who yells at you before you break the server. 💥

  • You say: "I expect this body to have a name", and TS says:
    "Cool, I’ll make sure you don’t forget it or spell it wrong".

  • Instead of trusting runtime, you trust the compiler to tell you what you’re messing up.

  • When you type routes, request bodies, and params — your API is self-documenting and safer.


✅ Summary Cheatsheet

FeatureTypeScript Usage
Express Types    Request, Response, NextFunction
TypeScript Body Parsing    Request<{}, {}, YourBodyType>
Middleware Typing    (err, req, res, next) with types
API Errors        Type Error in middlewares
Run w/ ts-node    npx ts-node src/index.ts
Build to JS    npx tsc ➜ run dist/index.js









4. Type-safe validation with Zod?

Ans.

🧾 1. Formal Definition

Zod is a TypeScript-first schema declaration and validation library. It lets you define schemas for your data and automatically provides type inference for those schemas — meaning you get runtime validation and compile-time type safety with no duplication.

🧠 With Zod, you define your validation schema once, and it gives you both validation and the TypeScript type.

📦 Install:

npm install zod

💻 2. Code Example + Explanation

🧩 Example: Validating a POST request body in Express

import express, { Request, Response } from "express"; import { z } from "zod"; const app = express(); app.use(express.json()); // ✅ Zod Schema const UserSchema = z.object({ name: z.string().min(3), email: z.string().email(), age: z.number().int().positive().optional(), }); // ✅ Inferred Type from Zod type User = z.infer<typeof UserSchema>; app.post("/register", (req: Request, res: Response) => { const parsed = UserSchema.safeParse(req.body); if (!parsed.success) { return res.status(400).json({ errors: parsed.error.format() }); } const user: User = parsed.data; res.status(201).json({ message: "User created", user }); }); app.listen(3000, () => console.log("Server running 🚀"));

🧠 Code Explanation:

What?Why?
z.object({...})Creates a Zod schema for validation
safeParse(req.body)Parses + validates safely; no throw
parsed.successBoolean — tells if validation passed
parsed.dataThe valid, type-safe data
z.infer<typeof Schema>Generates a fully accurate TS type from the schema automatically

🔥 Bonus: Schema Composition
const CredentialsSchema = z.object({ username: z.string(), password: z.string().min(6), }); const LoginSchema = CredentialsSchema.extend({ rememberMe: z.boolean().optional(), });

✅ You can extend, merge, and nest schemas for reusability.


😎 3. Easy / Slang Explanation

Think of Zod as your bouncer 🛡️ at the door of your API.

  • You tell Zod: “Only let in people with a name and valid email.”

  • Zod checks IDs (the data) before letting them into your app.

  • And while doing that, it also tells TypeScript: “Here’s exactly what kind of person just came in.”

So now:

  • Your app doesn’t crash from bad input ❌

  • Your editor helps you autocomplete data fields ✅

  • You write less boilerplate ✅✅


🧪 Common Zod Validators

ValidatorUse Case
z.string()    String values
z.number()    Numbers
.min(n)    Minimum length/number
.max(n)    Maximum length/number
.optional()    Marks field as optional
.email()    Validates email format
.int()    Must be integer
.safeParse(data)    Validates safely (returns result obj)

✅ Summary

FeatureBenefit
Define + validate schemaIn one place
TypeScript-awareInferred types from schema
Clean error handling.safeParse() avoids throwing
Works great in ExpressUse in route handlers for safe req.body
Powerful + composableNesting, merging, reusing schemas








5. TypeScript with API calls (using Axios or Fetch)?

Ans.

🧾 1. Formal Definition

When making API calls in TypeScript, you want to type the request and response data to ensure you handle server data correctly and avoid runtime errors. TypeScript helps by enforcing the structure of the data you send and receive.

  • Axios: A popular HTTP client that supports generic types to define response data.

  • Fetch: Native browser API for HTTP calls; combined with TypeScript, you manually type the parsed JSON responses.


💻 2. Code Examples + Explanation


⚡ Using Axios with Types

import axios from "axios"; // Define the expected shape of the response interface User { id: number; name: string; email: string; } async function fetchUser(userId: number): Promise<User> { const response = await axios.get<User>(`https://api.example.com/users/${userId}`); return response.data; // response.data is typed as User } (async () => { const user = await fetchUser(1); console.log(user.name); // TypeScript knows user has a 'name' property (string) })();

Explanation:

  • <User> tells Axios what type to expect in the response data.

  • TS ensures you use user.name as a string, no guesswork.


⚡ Using Fetch with Types

interface Post { id: number; title: string; body: string; } async function fetchPost(postId: number): Promise<Post> { const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`); if (!response.ok) throw new Error("Network error"); const data: Post = await response.json(); // Explicitly typing the JSON return data; } (async () => { const post = await fetchPost(10); console.log(post.title); // TS knows 'title' is a string })();

Explanation:

  • You type the JSON response manually (const data: Post).

  • TS helps prevent mistakes when accessing properties.


😎 3. Easy / Slang Explanation

Think of TypeScript + API calls like ordering food 🍕 with a clear menu:

  • You tell your waiter (Axios/Fetch): “I want a User with id, name, email.”

  • TS checks the kitchen output matches your order, so you don’t get mystery meat.

  • You get autocompletion and safety when using the data.


✅ Summary Table

FeatureAxiosFetch
How to type responseaxios.get<User>(url)const data: User = await response.json()
Error handlingTry/catch, Axios throws on bad statusManually check response.ok
Ease of useAuto-parses JSON, built-in interceptorsManual JSON parsing
Typing request bodyaxios.post<ReturnType, AxiosResponse<ReturnType>>(url, body)Manual JSON.stringify + typing















------------------------------------------------------------------------------

🧠 8. TypeScript in Real-world Apps



1. Typing Redux or Zustand state?

Ans.

✅ Redux (with Redux Toolkit)

1. Define your state slice type

interface CounterState { value: number; }

2. Create slice with typed state

const initialState: CounterState = { value: 0 }; const counterSlice = createSlice({ name: 'counter', initialState, reducers: { increment: (state) => { state.value += 1 }, decrement: (state) => { state.value -= 1 }, } }); export const { increment, decrement } = counterSlice.actions; export default counterSlice.reducer;

3. Setup typed RootState

const store = configureStore({ reducer: { counter: counterSlice.reducer, }, }); export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;

4. Use typed selectors and dispatch

const value = useSelector((state: RootState) => state.counter.value); const dispatch = useDispatch<AppDispatch>();

✅ Zustand

1. Define state and actions types

interface BearState { bears: number; increase: () => void; }

2. Create store with full typing

import { create } from 'zustand'; const useBearStore = create<BearState>((set) => ({ bears: 0, increase: () => set((state) => ({ bears: state.bears + 1 })), }));

3. Use store in components

const bears = useBearStore((state) => state.bears); const increase = useBearStore((state) => state.increase);

TL;DR

FrameworkWhere to TypeHow
ReduxRootState, initialState, reducersUse ReturnType and configureStore
ZustandStore creation functionPass generic to create<StoreType>()








2. Handling APIs (typing response/request)?

Ans.

When handling APIs with TypeScript (in Redux, Zustand, or plain fetch), you need to type both requests and responses to ensure correctness and developer experience.

Here's how to do it properly:


✅ 1. Typing API Response

Example: GET /user/:id

// Response from backend interface UserResponse { id: string; name: string; email: string; }

Usage with Fetch / Axios

const fetchUser = async (id: string): Promise<UserResponse> => { const res = await fetch(`/api/user/${id}`); if (!res.ok) throw new Error("Failed to fetch"); const data: UserResponse = await res.json(); return data; };

✅ 2. Typing API Request

Example: POST /login

interface LoginRequest { email: string; password: string; } interface LoginResponse { token: string; user: { id: string; name: string; }; }

Usage:

const login = async (body: LoginRequest): Promise<LoginResponse> => { const res = await fetch('/api/login', { method: 'POST', headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) throw new Error("Login failed"); return res.json(); };

✅ 3. Typing API in Redux (RTK Query)

If you're using RTK Query, it's built for strong typing.

interface Product { id: string; name: string; } const api = createApi({ reducerPath: 'api', baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ getProducts: builder.query<Product[], void>(), createProduct: builder.mutation<Product, { name: string }>(), }), });

✅ 4. Typing API in Zustand

interface Todo { id: string; title: string; completed: boolean; } interface TodoStore { todos: Todo[]; fetchTodos: () => Promise<void>; } const useTodoStore = create<TodoStore>((set) => ({ todos: [], fetchTodos: async () => { const res = await fetch('/api/todos'); const data: Todo[] = await res.json(); set({ todos: data }); }, }));

✅ 5. Centralize API Types (Best Practice)

Put all request/response types in a file like:

/types/api/index.ts

Example:

export interface RegisterRequest { email: string; password: string; name: string; } export interface RegisterResponse { userId: string; token: string; }






3. Handling form data with TypeScript?

Ans.

When handling form data with TypeScript, the key is to strictly type:

  1. The form state

  2. The input handlers

  3. The submit function

Here's how to handle it properly in different ways:


✅ 1. Typing Form Data Structure

interface LoginFormData { email: string; password: string; }

✅ 2. Controlled Components (React)

Minimal Example:

import { useState } from 'react'; const LoginForm = () => { const [formData, setFormData] = useState<LoginFormData>({ email: '', password: '', }); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value, })); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); console.log(formData); }; return ( <form onSubmit={handleSubmit}> <input type="email" name="email" value={formData.email} onChange={handleChange} /> <input type="password" name="password" value={formData.password} onChange={handleChange} /> <button type="submit">Login</button> </form> ); };

✅ 3. Using Refs (Less common, but valid)

const formRef = useRef<HTMLFormElement>(null); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const data = new FormData(formRef.current!); const email = data.get("email") as string; };

✅ 4. With Libraries (React Hook Form)

interface RegisterData { username: string; password: string; } const { register, handleSubmit } = useForm<RegisterData>(); const onSubmit = (data: RegisterData) => { console.log(data); }; <form onSubmit={handleSubmit(onSubmit)}> <input {...register("username")} /> <input type="password" {...register("password")} /> <button type="submit">Submit</button> </form>

✅ 5. Type Validation

You can combine it with zod, yup, or joi for schema-based validation.

const schema = z.object({ email: z.string().email(), password: z.string().min(6), });

TL;DR

TaskType With
useState       Interface for form shape
onChange    React.ChangeEvent<HTMLInputElement>
onSubmit    React.FormEvent<HTMLFormElement>
Schema Validation    zod, yup, or react-hook-form






4. Error handling (try/catch) with proper type guards?

Ans.

When using try/catch in TypeScript, the main issue is:
➡️ catch (err) is unknown by default.
So you need type guards to handle errors safely and properly.


✅ 1. Basic Pattern (Bad ❌)

try { await something(); } catch (err) { console.log(err.message); // ❌ TS error: Object is of type 'unknown' }

✅ 2. Correct Way: Use Type Guard ✅

function isError(err: unknown): err is Error { return err instanceof Error; } try { await something(); } catch (err) { if (isError(err)) { console.error('Error:', err.message); } else { console.error('Unknown error', err); } }

✅ 3. Handling Axios or Custom Error Types

import axios, { AxiosError } from "axios"; try { await axios.get('/some-api'); } catch (err) { if (axios.isAxiosError(err)) { console.error('Axios error:', err.response?.data); } else { console.error('Unknown error', err); } }

✅ 4. With Custom Error Class

class APIError extends Error { constructor(public statusCode: number, message: string) { super(message); } } function isAPIError(err: unknown): err is APIError { return err instanceof APIError; }

✅ 5. Inline Type Guard (Quick + Clean)

try { await fetchData(); } catch (err) { if (err instanceof Error) { console.log(err.message); } }

TL;DR

CaseHow to handle
Any unknown error    err instanceof Error
Axios    axios.isAxiosError(err)
Custom errors    err instanceof CustomError
Safe default       Treat catch (err) as unknown always







5. Project structure and best practices?

Ans.

Here’s a battle-tested project structure and best practices for scalable TypeScript projects — works whether you’re using React, Next.js, Node.js, or full-stack (MERN).


✅ 1. Folder Structure (Scalable & Clean)

/src /api → API routes or client (axios, fetch) /components → Reusable UI components /features → Domain-based logic (auth, user, dashboard) /auth AuthPage.tsx authSlice.ts → Redux/Zustand slice authAPI.ts → API calls authTypes.ts → TS interfaces/types /hooks → Custom hooks (useAuth, useForm) /lib → Utility functions (date, debounce, etc.) /pages → Page components (if React/Next.js) /store → Redux/Zustand store setup /types → Global shared types/interfaces /utils → Helpers (validators, mappers) /assets → Images, svgs, fonts /constants → Static config, enums, routes /middlewares → Express/Zustand/Next middlewares /services → API abstraction or business logic index.tsx / main.ts → App entry point

✅ 2. Best Practices

📦 Organize by Feature, not by Type

Instead of components/, reducers/, actions/ — group by feature domain:

/features/auth/ AuthForm.tsx authSlice.ts authAPI.ts

🧱 Use Absolute Imports

Use a tsconfig.json path alias:

"paths": { "@components/*": ["src/components/*"], "@features/*": ["src/features/*"], "@types/*": ["src/types/*"] }

🧪 Testing Structure

/__tests__/ auth.test.ts

Use Jest, Vitest, or Playwright for testing.


🌐 API Handling Pattern

// src/api/axios.ts export const axiosInstance = axios.create({ baseURL: '/api', withCredentials: true, });

🎯 Type First Development

  • Create types.ts or authTypes.ts inside each feature folder.

  • Always type API request & response.


🔒 Environment Config

Use .env, .env.local, etc.
Create a wrapper:

export const API_URL = process.env.NEXT_PUBLIC_API_URL!;

🗃️ Redux / Zustand Store Best Practice

  • Create per-feature slices

  • Global store should just register them

  • Split selectors & actions if complex


✅ 3. File Naming Conventions

TypeNaming
ComponentUserCard.tsx
Slice/APIuserSlice.ts
HookuseUser.ts
TypeuserTypes.ts
UtilsformatDate.ts


✅ 4. Linting & Formatting
  • ESLint + Prettier

  • Strict TypeScript (strict: true)

  • Use Husky + Lint-Staged for pre-commit


✅ 5. Other Pro Tips

  • Never import from ../.. → use aliases

  • Use .test.ts, .spec.ts, and co-locate test files

  • Group logic near where it's used (feature-focused)

  • Break components into Container + Presentational

  • Keep functions pure unless needed


🚀 TL;DR

Structure by feature, use absolute imports, write typed APIs, separate logic (UI, API, state), and enforce rules with lint + type checks.















--------------------------------------------------------------------------------

💼 9. Interview-Focused Topics





1. Deep understanding of type system (e.g. never, unknown, infer)?

Ans.

Here's a deep-dive into TypeScript's type system, focusing on advanced, misunderstood, or underused concepts like never, unknown, infer, and more — explained with clarity and practical code examples.


🔥 1. neverType that should not exist

✅ Definition:

A type that never happens. Used for:

  • Unreachable code

  • Exhaustive checks

🔍 Example:

function throwErr(msg: string): never { throw new Error(msg); } function handle(val: string | number) { if (typeof val === 'string') { return val.toUpperCase(); } else if (typeof val === 'number') { return val.toFixed(2); } else { const _: never = val; // ✅ Type check to ensure all cases handled } }

✅ TL;DR:

Use never to enforce exhaustive checks and signal impossible states.


🔥 2. unknownSafe alternative to any

✅ Definition:

unknown means "we don't know the type yet", but forces type narrowing before usage.

🔍 Example:

function handleData(data: unknown) { if (typeof data === 'string') { console.log(data.toUpperCase()); // ✅ safe now } // data.toUpperCase(); ❌ Error: Object is of type 'unknown' }

✅ Use Case:

  • API responses

  • Generic utilities

  • Safer than any


🔥 3. inferType extraction within conditional types

✅ Definition:

Used in conditional types to pull out a type from a structure.

🔍 Example 1: Extract array element type

type ElementType<T> = T extends (infer U)[] ? U : T; type A = ElementType<string[]>; // A = string type B = ElementType<number>; // B = number

🔍 Example 2: Extract return type of a function

type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never; type R = ReturnTypeOf<() => boolean>; // R = boolean

✅ Use Case:

  • Writing utilities like ReturnType<T>

  • Creating powerful reusable types


🔥 4. as constLiteral type inference

🔍 Without:

const roles = ['admin', 'user']; // roles: string[]

🔍 With:

const roles = ['admin', 'user'] as const; // roles: readonly ['admin', 'user']

Now you can create union types from it:

type Role = typeof roles[number]; // 'admin' | 'user'

🔥 5. keyof, typeof, in, extends

keyof — Union of keys:

type User = { name: string; age: number }; type Keys = keyof User; // "name" | "age"

typeof — Get type from variable:

const user = { id: 1, name: "faraz" }; type UserType = typeof user; // { id: number; name: string }

in — Map over keys:

type Flags<T> = { [K in keyof T]: boolean; }; type UserFlags = Flags<User>; // { name: boolean; age: boolean }

extends — Conditionals:

type IsString<T> = T extends string ? true : false; type A = IsString<"hello">; // true

🔥 6. Discriminated Unions + never check

type Shape = | { kind: 'circle'; radius: number } | { kind: 'square'; side: number }; function area(shape: Shape) { switch (shape.kind) { case 'circle': return Math.PI * shape.radius ** 2; case 'square': return shape.side ** 2; default: const _exhaustive: never = shape; // ✅ Ensures all cases are handled } }

TL;DR Table

KeywordUse CaseKey Insight
neverExhaustive checks, impossible pathsShould never be reached
unknownSafer alternative to anyForces type checking
inferExtract inner types in conditional typesUsed in reusable type utilities
as constPreserve literal typesEnables typeof array[number] tricks
keyofGet object keys as unionEnables mapped types
inIterate over keysUsed in mapped type definitions
extendsType conditionalsLike if/else at the type level







2. Advanced Generics usage?

Ans.

Let’s go deep into Advanced Generics in TypeScript — the stuff that separates "typing it" from "owning it".
This covers default types, constraints, conditional types, inference, and real-world use cases.


✅ 1. Generic Constraints with extends

function getLength<T extends { length: number }>(val: T): number { return val.length; } getLength("hello"); // ✅ getLength([1, 2, 3]); // ✅ getLength(123); // ❌ error: number doesn't have length

🔍 Use Case:

Ensure generic types have required shape.


✅ 2. Default Generic Types

type ApiResponse<T = any> = { data: T; status: number; }; const res: ApiResponse = { data: "ok", status: 200 }; // defaults to any

🔍 Use Case:

Make generics optional while maintaining flexibility.


✅ 3. Generic Functions With Multiple Type Params

function merge<T, U>(a: T, b: U): T & U { return { ...a, ...b }; } const result = merge({ name: "Faraz" }, { age: 22 }); // result: { name: string; age: number }

🔍 Use Case:

Combine objects while preserving both types.


✅ 4. Conditional Types + Generics

type IsArray<T> = T extends any[] ? "array" : "not-array"; type A = IsArray<string[]>; // "array" type B = IsArray<number>; // "not-array"

✅ 5. Infer With Generics

type UnwrapPromise<T> = T extends Promise<infer U> ? U : T; type A = UnwrapPromise<Promise<number>>; // number type B = UnwrapPromise<string>; // string

🔍 Use Case:

Extract internal types (e.g., for custom ReturnType, Awaited, etc.)


✅ 6. Generic Interfaces

interface ApiResult<T> { status: number; payload: T; } const result: ApiResult<{ id: string }> = { status: 200, payload: { id: "abc" }, };

✅ 7. Generic React Hook

function useLocalStorage<T>(key: string, initialValue: T): [T, (val: T) => void] { const [value, setValue] = useState<T>(() => { const json = localStorage.getItem(key); return json ? JSON.parse(json) : initialValue; }); const setStoredValue = (val: T) => { localStorage.setItem(key, JSON.stringify(val)); setValue(val); }; return [value, setStoredValue]; } const [user, setUser] = useLocalStorage<{ id: string }>("user", { id: "" });

✅ 8. Mapped Generics + Utility Types

type MakeOptional<T> = { [K in keyof T]?: T[K]; }; type User = { id: string; name: string; }; type OptionalUser = MakeOptional<User>; // { id?: string; name?: string }

✅ 9. Generic Component Props

interface ListProps<T> {
items: T[]; renderItem: (item: T) => JSX.Element; } function List<T>({ items, renderItem }: ListProps<T>) { return <>{items.map(renderItem)}</>; }

✅ 10. Generic Error Wrapper Utility

type Result<T> = { data: T; error: null } | { data: null; error: string }; function safeExecute<T>(fn: () => T): Result<T> { try { return { data: fn(), error: null }; } catch (e) { return { data: null, error: (e as Error).message }; } }

TL;DR Cheatsheet

FeatureSyntaxPurpose
Constraint<T extends SomeType>Limit what types T can be
Default<T = DefaultType>Use fallback when not provided
Inferenceinfer UPull out a sub-type from a structure
Conditional TypesT extends U ? X : YType-level branching
Mapped Types{ [K in keyof T]: T[K] }Transform or remap object keys
Utility TypesPartial<T>, Pick<T>, Omit<T>Base templates for reuse







3. Utility Types with real-world examples?

Ans.

Here’s a real-world breakdown of the most useful TypeScript utility types with concrete, practical examples — not just syntax, but why you'd actually care.


🛠️ 1. Partial<T>

Makes all properties optional

interface User { id: string; name: string; email: string; } function updateUser(id: string, updates: Partial<User>) { // updates can have one or more fields } updateUser("123", { email: "new@mail.com" });

📌 Use when: You’re doing patch/update forms, or building draft-like state.


🛠️ 2. Required<T>

Makes all properties mandatory

type DraftUser = { name?: string; email?: string; }; type FinalUser = Required<DraftUser>; // now both are required

📌 Use when: You want to force full completion (e.g., finalizing drafts, schemas).


🛠️ 3. Readonly<T>

Locks object properties

const user: Readonly<User> = { id: "1", name: "Faraz", email: "faraz@mail.com" }; // user.name = "new" ❌ Cannot assign

📌 Use when: You want immutability, like in Zustand/Redux or constants.


🛠️ 4. Pick<T, K>

Select only specific keys

type UserPreview = Pick<User, "id" | "name">;

📌 Use when: You want limited view models (e.g. list view, dropdowns).


🛠️ 5. Omit<T, K>

Exclude specific keys

type UserWithoutEmail = Omit<User, "email">;

📌 Use when: You're hiding sensitive fields, or creating simplified versions.


🛠️ 6. Record<K, T>

Typed object maps

type Role = "admin" | "user"; type Permissions = Record<Role, string[]>; const permissions: Permissions = { admin: ["delete", "update"], user: ["read"] };

📌 Use when: You’re building role maps, config dictionaries, or enum-based objects.


🛠️ 7. Exclude<T, U>

Removes from union

type Status = "loading" | "success" | "error"; type CleanStatus = Exclude<Status, "loading">; // "success" | "error"

📌 Use when: You want to filter enums or unions based on context.


🛠️ 8. Extract<T, U>

Keeps only matching union types

type Input = "text" | "checkbox" | "radio"; type OnlyForm = Extract<Input, "checkbox" | "radio">; // "checkbox" | "radio"

📌 Use when: You want to narrow down types to a specific subset.


🛠️ 9. ReturnType<T>

Gets return type of a function

function getUser() { return { id: 1, name: "Faraz" }; } type User = ReturnType<typeof getUser>; // { id: number; name: string }

📌 Use when: You want to avoid manual duplication of output types.


🛠️ 10. NonNullable<T>

Remove null and undefined from a type

type MaybeString = string | null | undefined; type SafeString = NonNullable<MaybeString>; // just string

📌 Use when: You’re sure the value is present after validation.


🛠️ 11. Parameters<T>

Gets function’s parameter types as a tuple

function log(message: string, level: number) {} type Args = Parameters<typeof log>; // [string, number]

📌 Use when: You’re reusing argument types in wrappers or middleware.


TL;DR – Pick the Right Tool

UtilityUse When You...
PartialWant optional form fields or patch updates
RequiredWant to enforce complete object structure
ReadonlyNeed immutability or frozen configs
PickWant a smaller view of a type
OmitWant to hide/remove keys
RecordMap keys to typed values
ExcludeFilter out values from a union
ExtractKeep only certain union values
ReturnTypeReuse function outputs
NonNullableEliminate null/undefined for safe usage
ParametersExtract argument types for reuse/wrapping





4. Differences between interface vs type?

Ans.

Here’s the real, no-fluff comparison between interface vs type in TypeScript — when to use which, how they differ under the hood, and where one outperforms the other.


🔍 1. Basic Syntax & Similarities

interface User { id: string; name: string; } type UserType = { id: string; name: string; };

✅ Both are structurally identical here. You can use them interchangeably for objects.


⚔️ 2. Key Differences

Featureinterfacetype
Extending✅ Can extend other interfaces✅ Can extend via intersection
MergingDeclaration merging❌ No merging allowed
Unions❌ Not supported✅ Supports unions easily
Tuples, Primitives❌ Not supported✅ Can alias any type
Mapped/Conditional❌ Not directly✅ Full support

📌 3. Declaration Merging (interface only)

interface Person { name: string; } interface Person { age: number; } // Merged: { name: string; age: number }

🔸 This is great for 3rd-party libraries or plugin patterns.


🔀 4. Unions & Intersections (type only)


type APIResponse = { data: string } | { error: string }; // ✅

type Admin = { role: 'admin' }; type User = { name: string }; type AdminUser = Admin & User; // ✅ intersection

Interfaces can't do this — only types can.


🔧 5. Mapped & Conditional Types (type only)

type ReadonlyProps<T> = { readonly [K in keyof T]: T[K]; }; type Result<T> = T extends string ? string[] : number[];

type is more powerful for type-level programming.


🧩 6. Extending

Interface:

interface Animal { species: string; } interface Dog extends Animal { breed: string; }

Type:

type Animal = { species: string }; type Dog = Animal & { breed: string };

Both work — interfaces use extends, types use &.


🧠 7. Best Practices (When to Use What)

Use CaseUse interfaceUse type
Public API surface / libraries✅ Interface (mergeable, extendable)❌ (type alias can't be reopened)
Component props / React✅ Interface (common style)Also fine
Union types / conditional logic✅ Required
Tuples / primitives / aliases✅ Only type can do this
Advanced meta types & mapping✅ Use type

🧨 TL;DR: Type vs Interface

Criteriainterfacetype
Extendable✅ Yes (with extends)✅ Yes (with &)
Declaration merging✅ Yes❌ No
Union / Tuple support❌ No✅ Yes
Primitives❌ No✅ Yes
Complex mapped types❌ Limited✅ Full power
Performance✅ Slightly better in tooling⚠️ Slightly slower in complex cases

💡 Verdict

  • Use interface for objects and public contracts

  • Use type for everything else: primitives, unions, mapped types, conditionals, etc.








5. When to use which type system feature?

Ans.

Knowing when to use each TypeScript type system feature is crucial to avoid overengineering or missing type safety. Here’s a practical guide for deciding between type, interface, class, enum, union, intersection, utility types, and advanced features.


1. interface vs type

Use interface when:

  • You’re defining object shapes (especially for public APIs or libraries).

  • You need declaration merging (e.g., extending Express.Request).

  • You want a clear contract for classes or React component props.


interface User { id: string; name: string; }

Use type when:

  • You need unions, intersections, tuples, or primitive aliases.

  • You’re doing advanced type transformations (mapped/conditional types).

  • You want concise compositions (e.g., type User = Admin & BasicUser).

type Status = 'pending' | 'success' | 'error';

2. class

Use classes when:

  • You need runtime behavior + static typing (methods, inheritance).

  • You’re working with frameworks like NestJS or OOP patterns.

  • You want instance checks (instanceof) and private fields.


class Animal { constructor(public name: string) {} speak() { console.log(`${this.name} speaks`); } }

3. enum vs union

Use enum when:

  • You need a runtime object (e.g., accessible values at runtime).

  • You want reverse mapping or numeric values.

enum Role { ADMIN = "admin", USER = "user" }

Use string unions when:

  • You need type safety without runtime overhead.

  • You want flexibility in type compositions.

type Role = "admin" | "user";

4. union vs intersection

  • Union (|) — use when a value is one of multiple types:

    type Input = string | number;
  • Intersection (&) — use when a type combines multiple structures:

    type AdminUser = Admin & User;

5. never vs unknown vs any

  • never — Use for impossible states, exhaustive checks:

    const _exhaustive: never = value;
  • unknown — Use when type is unknown but must be narrowed before usage:

    function handle(data: unknown) { if (typeof data === 'string') console.log(data.toUpperCase()); }
  • any — Avoid unless dealing with dynamic 3rd-party data or gradual migration.


6. infer & Conditional Types

  • Use infer when extracting types dynamically, like ReturnType:


    type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
  • Use conditional types for type-level logic:


    type IsString<T> = T extends string ? true : false;

7. Utility Types

  • Use Partial<T> when building update forms or PATCH requests.

  • Use Pick<T, K> or Omit<T, K> for selective data exposure.

  • Use Readonly<T> for immutability in state stores (Redux/Zustand).

Example:

type UserPreview = Pick<User, "id" | "name">;

8. Generics

  • Use generics when your function/type must be reusable with different types:

    function identity<T>(value: T): T {
    return value; }
  • Use constraints (<T extends Something>) to restrict:

    function logLength<T extends { length: number }>(val: T) { console.log(val.length); }

9. Discriminated Unions + never

Use this for exhaustive checks:

type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number }; function area(s: Shape) { switch (s.kind) { case "circle": return Math.PI * s.radius ** 2; case "square": return s.side ** 2; default: const _exhaustive: never = s; throw new Error(`Unhandled case: ${_exhaustive}`); } }

10. When to Combine Features

  • React props → Use interface or type with Pick/Partial.

  • APIs → Use type for response/request structures + Partial for patch routes.

  • Complex state → Use type + utility types + generics to avoid duplication.


Rule of Thumb

  • Objects/contractsinterface

  • Unions, primitives, tuples, conditionalstype

  • Reusable code with type inference → Generics + infer

  • Immutability or transformations → Utility types (Readonly, Omit, etc.)

  • Validation & exhaustive checksnever, discriminated unions.








6. Refactoring JavaScript to TypeScript?

Ans.

Refactoring JavaScript to TypeScript effectively means introducing types incrementally and safely while keeping your code functional. Here’s a practical step-by-step approach with tips to avoid common pitfalls and maximize benefits:


1. Rename files: .js.ts / .tsx

  • Start by just renaming your files.

  • Your JS code will still run but TS compiler will check it.

  • Use // @ts-nocheck at the top of complex files temporarily if needed.


2. Enable strict TS compiler options

In tsconfig.json, set:

{ "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "esModuleInterop": true, "allowJs": true, "checkJs": false } }
  • Turn these on gradually as your confidence grows.

  • You can start with "noImplicitAny": false and enable later.


3. Add types incrementally

  • Start small: Add types for function parameters and return types.

  • Type your objects and arrays.

  • Use interfaces or type aliases for data shapes.

Example:

// JS function greet(user) { return "Hello " + user.name; } // TS interface User { name: string; } function greet(user: User): string { return "Hello " + user.name; }

4. Use any sparingly, replace with unknown when unsure

  • any disables all type checks and defeats TS purpose.

  • unknown forces you to narrow the type before usage.


function handle(data: unknown) { if (typeof data === 'string') { console.log(data.toUpperCase()); } }

5. Convert default exports and imports

Make sure your import/export syntax matches TS expectations, often:

// Instead of CommonJS require: import express from 'express'; // or default exports: export default MyComponent;

6. Leverage existing type definitions

  • Use DefinitelyTyped @types/... for libraries (e.g., npm i -D @types/lodash)

  • This improves autocomplete and prevents any leaking.


7. Handle third-party untyped libraries

  • Temporarily declare minimal types or use declare module 'libname';

  • Avoid large-scale any if possible.


8. Use type assertions carefully

If TS can’t infer correctly:

const input = document.getElementById('input') as HTMLInputElement;

But prefer narrowing with if (input instanceof HTMLInputElement).


9. Introduce interfaces for props and state in React

interface Props { title: string; count: number; } const MyComponent: React.FC<Props> = ({ title, count }) => { ... }

10. Refactor stepwise

  • Pick one module or feature at a time.

  • Test that everything still works.

  • Fix TS errors before moving on.

  • Use ts-ignore or @ts-expect-error sparingly to unblock, but plan to remove.


Bonus: Tools

  • Use VSCode TS features — hover, error squiggles, quick fixes.

  • Run tsc --noEmit to check for errors without building.

  • Use ts-migrate or typescript-eslint autofix scripts for large codebases.


TL;DR

  • Rename files .ts(x)

  • Add types incrementally (start with function signatures)

  • Use strict compiler options but ease in slowly

  • Use interfaces/type aliases for objects

  • Avoid any and prefer unknown

  • Leverage @types for libraries

  • Refactor step-by-step, keep the app working










7. Performance benefits and compilation process?

Ans.

1. Performance Benefits

TypeScript is a compile-time tool, not a runtime optimizer, so it doesn't make your JavaScript run faster directly. But it indirectly helps improve performance by:

  • Early error detection: Catches bugs during development that might cause runtime crashes or inefficient logic.

  • Better tooling & IDE support: Autocomplete, refactoring, and type safety reduce costly debugging and improve developer productivity.

  • Safer refactoring: With guaranteed type correctness, you can optimize and improve code confidently without breaking things.

  • Enforcing immutability and pure functions: With typings, you’re more likely to write predictable, side-effect-free code that optimizes well.

In short: TS improves developer productivity and code quality, leading to better, maintainable, and potentially faster applications over time, but it does not optimize runtime performance by itself.


2. TypeScript Compilation Process

  1. Parsing & Type Checking:

    • TS parses your .ts / .tsx files and builds an Abstract Syntax Tree (AST).

    • It runs a type-checker that enforces all type rules, detects errors, and performs type inference.

    • If errors exist, compilation can fail (depending on your config).

  2. Transpilation (Emit JavaScript):

    • TS transpiles your code to plain JavaScript, usually targeting ES5 or ES6+ depending on your tsconfig.json.

    • Strips all type annotations and TS-only syntax.

    • Does not perform minification or optimization by default — bundlers or other tools handle that.

  3. Declaration File Generation (.d.ts files):

    • Optionally generates declaration files to expose types to consumers, useful for libraries.

  4. Integration with Build Tools:

    • TS compiler can be integrated with tools like Webpack, Babel, or esbuild.

    • Babel can transpile TS syntax without type checking (faster builds, but separate type checking needed).

    • tsc handles full type checking + transpilation.


3. Compilation Settings Impacting Performance

  • "incremental": true and "build" mode speeds up recompilation by caching.

  • "skipLibCheck": true skips checking .d.ts files for faster builds (but less strict).

  • "noEmitOnError": true prevents output if there are errors, forcing fixes early.


Summary

AspectDetails
Runtime PerformanceNo direct improvement by TS
Developer EfficiencySignificant gains in bug prevention & refactoring confidence
Compilation StepsParsing → Type Checking → Transpiling to JS
OutputClean JS, no types, target configurable
Build OptimizationDone by bundlers/minifiers, not TS compiler







8. Real-world scenarios (System Design with TS)?

Ans.

1. API Contract Design

  • Define shared types/interfaces for request/response bodies between backend & frontend.

  • Use tools like tsc or openapi-typescript to generate types from API specs.

  • This prevents mismatches, enabling IDE autocomplete and compile-time validation.


// Shared types in `/types/api.ts` export interface User { id: string; name: string; email: string; } export interface ApiResponse<T> { status: number; data: T; error?: string; }

System benefit: Contracts are explicit and evolve safely.


2. State Management

  • Use TypeScript interfaces or types for global state shapes (Redux, Zustand).

  • Enforce strict typing for actions, reducers, selectors to avoid bugs.


interface UserState {
currentUser: User | null; loading: boolean; error?: string; } type UserAction = | { type: "LOGIN_SUCCESS"; payload: User } | { type: "LOGOUT" } | { type: "ERROR"; payload: string };

System benefit: Reliable state transitions and predictable app behavior.


3. Domain Modeling

  • Model your domain entities with interfaces/types or classes.

  • Add validation layers (e.g., Zod schemas) tied to TS types.

  • Use generics and mapped types for complex domain logic.


interface Product { id: string; name: string; price: number; metadata?: Record<string, any>; }

System benefit: Domain logic is type-safe and easier to refactor.


4. Component & UI Design (React / Vue / Angular)

  • Strongly type component props, context, and hooks.

  • Use generics for reusable UI components.

interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; } function List<T>({ items, renderItem }: ListProps<T>) { return <>{items.map(renderItem)}</>; }

System benefit: Prevents runtime bugs and improves reusability.


5. Error Handling & Resilience

  • Use discriminated unions and exhaustive checks to handle API errors.

  • Type-safe error boundaries and fallback UI.


type ApiResult<T> = | { status: "success"; data: T } | { status: "error"; error: string }; function handleResult<T>(result: ApiResult<T>) { if (result.status === "error") { // handle error safely } }

System benefit: Robust error handling with zero surprises.


6. Middleware & Pipeline Systems

  • Use TS generics to type request/response objects flowing through middleware.

  • Validate that each middleware transforms or augments data properly.


type Middleware<Req, Res> = (req: Req) => Promise<Res>; const authMiddleware: Middleware<Request, AuthenticatedRequest> = async (req) => { // add user info to request };

System benefit: Middleware pipelines become composable and safe.


7. Code Generation & Automation

  • Use TS for generating code based on schemas (GraphQL, OpenAPI).

  • Keep types in sync across client/server with generated files.

System benefit: Single source of truth reduces bugs and saves dev time.


Summary Table

System AspectTS RoleBenefit
API ContractShared request/response typesPrevent mismatches
State ManagementTyped state/actionsPredictability & bug reduction
Domain ModelingInterfaces, generics, validationRobust, refactor-friendly design
UI ComponentsStrong prop & hook typingSafe reusable UI
Error HandlingDiscriminated unionsPredictable failures
MiddlewareGeneric pipeline typesComposability & type safety
Code GenerationAuto-generate types & clientsSync and consistency

Comments

Popular posts from this blog

ReactJs Notes

NextJS Notes

HTML NOTES