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. 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:
In JavaScript, this would NOT throw an error until runtime. TypeScript gives you an error during development.
💻 2. Code Example + Explanation
✅ Code Breakdown:
-
name: string
: This tells TS that thename
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.
✅ 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):
-
Installs
tsc
(TypeScript Compiler) globally so you can use it anywhere via CLI.
📁 Create a project and initialize it:
This creates a tsconfig.json
file.
🧾 Sample tsconfig.json
:
💻 2. Code Example + Explanation
Folder Structure:
src/index.ts
:
Compile the project:
------------------------
It will generate the compiled JavaScript in dist/index.js
:
Explanation:
-
npx tsc
reads fromtsconfig.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 insidetsconfig.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. 🔥
✅ 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:
Type | Description | Example |
---|---|---|
string | Text values | let name: string = "Ayan" |
number | Numeric values (int, float, etc.) | let age: number = 22 |
boolean | true or false values | let isAdmin: boolean = false |
any | Turns off type checking (accepts any type) | let data: any = "hello" |
unknown | Like any , but safer — must be checked before using | let userInput: unknown = 5 |
void | Functions that return nothing | function log(): void {} |
never | Functions that never return (e.g. throw errors) | function fail(): never {} |
null | A null value | let x: null = null |
undefined | A variable that hasn’t been assigned | let y: undefined = undefined |
💻 2. Code Example + Explanation
🧠 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
isunknown
— TypeScript forces you to check its type before using it. -
logMessage()
returns nothing → type isvoid
. -
throwError()
never finishes — it throws an error → type isnever
. -
null
andundefined
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 (likethrow
) -
null
: literally nothing. -
undefined
: "I exist, but nobody gave me a value."
TS lets you write:
Now if you try:
This way, TypeScript watches your back and stops you from doing dumb stuff before you even run the code.
✅ 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:
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
🔍 Code Breakdown:
-
isLoggedIn
is inferred as aboolean
, because you gave ittrue
. -
multiply()
parameters are explicitly typed, but the return type is inferred asnumber
based on the operationx * y
. -
result
is inferred asnumber
because it holds the return value ofmultiply
.
✅ 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:
TS goes: “Hmm… that’s a string, got it.” ✅
You don’t have to write : string
.
BUT — if you mess it up later:
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.
✅ 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.”
🔸 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.”
💻 2. Code Example + Explanation
🔹 Union Type Example:
🧠 Explanation:
-
The
id
parameter accepts either a number OR a string. -
TS will prevent any other types from being passed.
🔸 Intersection Type Example:
🧠 Explanation:
-
DevPerson
combines bothPerson
andDeveloper
. -
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:
You're saying: “Bro, I’ll take either Tea or Coffee, but not both.”
So:
🧩 Intersection (&
): “All Together Now”
Now imagine you’re building a character that’s both a wizard and a warrior:
Your Battlemage
gotta have both mana AND strength.
So:
If you leave out one, TypeScript says:
“Ayo, you're missing something!”
🔥 Summary:
Concept | Symbol | Meaning | Think of it as... |
---|---|---|---|
Union Type | ` | ` | One of the types (OR) |
Intersection | & | All types combined (AND) | “Include everything” |
✅ 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:
💻 2. Code Example + Explanation
💬 Literal type in action:
🧠 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:
Now:
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
:
That locks in the values and gives you literal type power from objects. 💪
✅ 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:
-
By default,
Success = 0
,Error = 1
,Loading = 2
You can use it like:
💻 2. Code Example + Explanation
🔢 Numeric Enum:
🔤 String Enum:
🧠 Explanation:
-
Enums replace magic numbers or strings with descriptive labels.
-
Direction.Left
makes more sense than just2
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:
Which is risky (what if you typo it?), you write:
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)
Enums let you go both ways — from name to value AND value to name (only in numeric enums).
✅ 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.
🔹 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.
✅ Key Differences
Feature | type | interface |
---|---|---|
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
Both are identical here.
✅ Example 2: Extending
Both ways work, but interface
uses extends
while type
uses &
.
✅ Example 3: Declaration Merging (Only works with interfaces)
This doesn’t work with type
. Type aliases cannot be re-declared.
✅ Example 4: Union & Tuple Support (Only with type
)
😎 3. Easy/Slang Explanation
Okay, here’s how you remember it:
🧱 Interface = Blueprint for Objects
Think of interface
like a class blueprint:
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. Usetype
for everything else like primitives, unions, tuples, or when combining types with&
or|
."
✅ 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 astring
, second is anumber
.”
📌 Example:
💻 2. Code Example + Explanation
🧠 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]
✅ 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:
Here, someValue
is typed unknown
, but you assert it as a string
to access .length
.
💻 2. Code Example + Explanation
🧠 Explanation:
-
getElementById
returnsHTMLElement | null
. -
But you know
my-input
is an<input>
element, so you assert it asHTMLInputElement
. -
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:
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 (useas
instead).
✅ 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
andvar
are usually inferred with wider types. -
TypeScript treats
const
variables with literal values as immutable types unless explicitly widened.
💻 2. Code Example + Explanation
🧠 Explanation:
-
const pi = 3.14;
TypeScript infers the literal type3.14
(a special subtype of number). This meanspi
can only ever hold the exact value3.14
unless you explicitly widen the type. -
let age = 30;
Inferred asnumber
, can be updated but not re-declared in the same scope. -
var username = "Ayan";
Inferred asstring
, 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 withlet
orconst
. -
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:
TS locks that in as the exact word "happy", not just any string. So if you try:
But with let
:
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:
✅ 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"
.
b) in
Narrowing
Checks if a property exists in an object.
c) instanceof
Narrowing
Checks if an object is an instance of a class or constructor function.
😎 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:
Narrowing gives you freedom to access specific stuff safely.
📦 2. Functions and Objects
🧾 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 twonumber
parameters and returns anumber
:
💻 2. Code Example + Explanation
🔍 Explanation:
-
(name: string, age: number) => string
is the function type. -
It means: this function takes a
string
and anumber
, and returns astring
. -
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 💥).
🧾 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:
💻 2. Code Example + Explanation
🔍 Explanation:
-
hobby: string = "coding"
→ Default Parameter: if no value is passed, it uses"coding"
by default. -
country?: string
→ Optional 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?
🔹 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 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:
💻 2. Code Example + Explanation
🧮 Rest Parameters
🔍 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
🔍 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.
🧾 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:
Now any function that matches this structure can be assigned to GreetFn
.
💻 2. Code Example + Explanation
🔍 Explanation:
-
type MathOperation = (a: number, b: number) => number
→ defines a function type alias. -
add
andmultiply
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 sayMathOperation
, 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.
🧾 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:
Here, callback
is a parameter with the type of a function that takes no arguments and returns nothing (void
).
💻 2. Code Example + Explanation
🔍 Explanation:
-
callback: (data: string) => void
means:-
The callback must accept a single
string
parameter (data
) -
And it must return
void
(nothing)
-
-
Inside
processUserInput
, we callcallback(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.
🧾 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:
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:
This means: any property with a string key will have a number value.
💻 2. Code Example + Explanation
🧱 Object Type Example
🔍 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 theProduct
type—no extra properties, and all types must match.
🔑 Index Signature Example
🔍 Explanation:
-
The type says: any string key is allowed, and the value must be
string
ornumber
. -
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:
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.
🧾 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:
In this case, the id
is read-only — it can be set once and never updated.
💻 2. Code Example + Explanation
🔍 Explanation:
-
readonly vin: string
means: you can setvin
when creatingmyCar
, 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.” 🙅♂️
🧾 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:
💻 2. Code Example + Explanation
✅ Object Destructuring with Type Annotation
🔍 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 andage
as a number.
✅ Array Destructuring with Type Annotation
🔍 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
andage
. Lemme just pull those out.”
So you're like:
But TypeScript being the grammar police says:
“Cool, but tell me what type those are!”
So you add:
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. 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:
You can use type
to define object shapes too:
🔹 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:
💻 2. Code Examples + Explanations
✅ Basic Object Types (They look similar here)
🔍 Explanation:
Both define an object with name
and age
. Functionally, these are the same for most use cases.
✅ Extending
🔍 Explanation:
-
interface Dog extends Animal
is clean and semantic. -
type PersianCat = Cat & { ... }
is more “math-style” (intersection).
✅ Declaration Merging (Only Interfaces can do this)
🔍 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)
🔍 Explanation:
-
type
works great for function types. -
interface
can do it too, but with a slightly clunky syntax:
✅ Tuple and Union Support (Only type
)
🔍 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?
Scenario | Use interface | Use 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 |
🧾 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
usesextends
to inherit from other interfaces -
type
uses intersection (&
) for a similar effect
✅ Example (interface):
Now Employee
has all properties of Person
+ jobTitle
.
💻 2. Code Example + Explanation
✅ Using extends
with Interface
🔍 Explanation:
-
AdminUser
extendsBaseUser
, so it inheritsid
andusername
. -
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)
🔍 Explanation:
-
DiscountedProduct
combinesProduct
and another object withdiscountPercent
. -
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:
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 &
:
That’s like saying:
“Take all the Animal stuff, and glue this new stuff to it.”
Quick Summary:
Use Case | interface extends | type & 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 |
✅ 1. keyof
🧾 Formal Definition
keyof
is a TypeScript operator that returns the union of property names (keys) of a given type.
✅ Example:
💻 Code Example + Explanation
🔍 Explanation:
-
keyof Car
returns a union of keys:"make" | "model" | "year"
. -
The
getValue
function takes any valid property key ofCar
.
🧃 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:
💻 Code Example + Explanation
🔍 Explanation:
-
typeof rgb
captures the exact type of thergb
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:
💻 Code Example + Explanation
🔍 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:
💻 Code Example + Explanation
🔍 Explanation:
-
infer R
means: "try to figure out the return typeR
" -
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:
💻 Code Example + Explanation
🔍 Explanation:
-
document.getElementById(...)
returnsHTMLElement | 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
Keyword | What it does | Think 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” 😅 |
🧾 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:
✅ Example:
💻 2. Code Example + Explanation
🔍 Explanation:
-
[K in keyof User]
loops through all keys ofUser
("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 toboolean
).
🧃 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
🧾 1. Formal Definition
Conditional Types in TypeScript allow you to define types based on a condition, just like if...else
but for types.
✅ Syntax:
This means:
-
If type
T
is assignable to typeU
, then the result is typeX
-
Otherwise, it is type
Y
✅ Example:
💻 2. Code Example + Explanation
🔍 Explanation:
-
ResponseType<T>
uses nested conditional types -
If
T
is astring
, 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:
This is TypeScript saying:
“If T is 'Ayan', then it’s true. Otherwise, nope.”
So:
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
Here:
-
infer R
means “try to guess the return type” -
GetReturn<Fn>
extracts thenumber
type from() => number
🧾 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:
Utility | Purpose |
---|---|
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>
👉 Makes every property optional (?
).
2️⃣ Required<T>
👉 Opposite of Partial
— forces all optional fields to be required.
3️⃣ Pick<T, K>
👉 Extract specific keys from a type.
4️⃣ Omit<T, K>
👉 Remove specified keys.
5️⃣ Record<K, T>
👉 Creates a type with fixed keys and uniform value types.
6️⃣ Exclude<T, U>
👉 Removes specific types from a union.
7️⃣ Extract<T, U>
👉 Keeps only types assignable to the second type.
8️⃣ NonNullable<T>
👉 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 ofnull
andundefined
— no maybes allowed!”
📌 Summary Table
Utility | What 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 |
🧾 1. Formal Definition
Discriminated Unions (also called Tagged Unions) are a pattern in TypeScript that combines:
-
Union types
-
Common "discriminant" property (usually a string literal)
-
Type narrowing using that property
This makes it super easy and type-safe to distinguish between different object types inside a union.
✅ Structure:
Here, "kind"
is the discriminant.
💻 2. Code Example + Explanation
🔍 Explanation:
-
The
Shape
union combines bothCircle
andSquare
. -
Both types share a
kind
property with literal values. -
TypeScript uses
shape.kind
to narrow the object type inside theswitch
. -
When
kind
is"circle"
, TS knows it's aCircle
and allows access toradius
.
🧠 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.
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:
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
Concept | Role |
---|---|
kind field | Discriminant/tag |
`Shape = A | B |
switch (shape.kind) | Type narrowing (safe & automatic) |
Usage | Pattern matching in a clean way 💯 |
🧾 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:
Here, Greeting
can be any string that starts with "Hello, "
followed by any string.
💻 2. Code Example + Explanation
Example 1: Basic Template Literal
🔍 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
🔍 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:
But for types:
TypeScript even auto-generates combos when you use unions:
It’s like telling TypeScript:
“Hey, go make all the strings that look like this pattern, and lock them down as types.”
✅ Summary Table
Concept | Example | Meaning |
---|---|---|
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 |
🧾 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:
💻 2. Code Example + Explanation
Example: Nested Comment Thread
🔍 Explanation:
-
Comment
has an optionalreplies
field. -
replies
is an array of the sameComment
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.
🧃 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:
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
Concept | Description |
---|---|
Recursive type | A type that references itself |
Syntax | type T = { key: T } or key?: T[] |
Use Cases | Trees, comments, menus, file systems |
TS support | Yes, fully supported with type aliases |
🧾 1. Formal Definition
TypeScript supports access modifiers to control how class members (properties or methods) are accessed:
Modifier | Visibility | Usage |
---|---|---|
public | Accessible from anywhere | Default if no modifier is given |
private | Accessible only within the class | Completely hidden outside |
protected | Accessible within the class and subclasses | Not outside the class hierarchy |
readonly | Can be read but not changed | Works with any access level |
💻 2. Code Example + Explanation
🔍 Explanation:
-
name
is public, so it's accessible anywhere. -
password
is private, so only accessible inside theUser
class. -
email
is protected, so accessible insideUser
andAdmin
, 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.
Modifier | Real-Life Analogy |
---|---|
public | Like your public bio — anyone can see it 🧑🤝🧑 |
private | Like your DMs — only you can access them 🔐 |
protected | Like a private family group — only you + fam 👨👩👧 |
readonly | Like your birthdate — visible, but can’t be changed 🎂 |
You're saying:
“Bro, don’t let anyone outside this class touch or even peek at this.”
And when you use:
It’s like:
“Here’s your ID — you can look at it, but don’t even think about changing it.”
✅ Summary Table
Modifier | Who can access it? | Can change it? |
---|---|---|
public | Anyone | Yes |
private | Only inside the class | Yes |
protected | Class + subclasses | Yes |
readonly | Wherever it’s visible | ❌ No (after init) |
🧾 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:
-
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
🔍 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:
is like saying:
“Yeah, you can see your bank balance.”
And this:
is like saying:
“No putting negative money in your account, bro.”
✅ Summary Table
Feature | Purpose | Syntax | Example |
---|---|---|---|
get | Read a private field | get name() | console.log(obj.name) |
set | Write to a field | set name(v) | obj.name = "Ayan" |
Benefit | Encapsulation + Logic | Add validation, transform data |
🧾 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:
You can’t create new Animal()
, but you can extend it.
💻 2. Code Example + Explanation
🔍 Explanation:
-
Animal
is abstract, so you can’t donew Animal()
. -
It has an abstract method
makeSound()
→ forces subclasses likeDog
to implement it. -
move()
is a regular method, so it can be inherited and used as-is. -
Dog
implementsmakeSound()
and inheritsmove()
.
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:
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:
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
Feature | Abstract Class |
---|---|
Can instantiate? | ❌ No |
Can extend? | ✅ Yes |
Abstract methods? | ✅ Must be implemented in child |
Regular methods? | ✅ Can be inherited |
🧾 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:
💻 2. Code Example + Explanation
Example: Vehicle Interface
🔍 Explanation:
-
Vehicle
is an interface that defines two things:-
brand
: a string -
drive()
: a method that returns nothing (void)
-
-
Car
is a class that implementsVehicle
, 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 todrive()
."
When your class says implements Vehicle
, it’s like replying:
"Bet. I’ve got a
brand
and I candrive()
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:
🧠 The goal: enforce structure and avoid bugs where someone forgets to add an expected method.
✅ Summary Table
Concept | Interface | Class Implements It |
---|---|---|
Purpose | Structure/Contract | Fulfills the contract |
Method required? | ✅ Yes | ✅ Must be implemented |
Property check? | ✅ Yes | ✅ Must match |
Multiple? | ✅ Yes (comma-separated) | ✅ implements A, B |
🧾 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:
💻 2. Code Example + Explanation
Example: Utility Class
🔍 Explanation:
-
static count
: Shared counter value across all uses ofCounter
. -
static increment()
: Modifies the staticcount
. -
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:
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:
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
Feature | Description |
---|---|
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 |
🧾 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:
💻 2. Code Example + Explanation
📦 Generic Function
🔍 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
🔍 Explanation:
-
Storage<T>
is a generic class — it can store any type, but only one type per instance. -
You create a
Storage<number>
orStorage<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:
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:
📦 Generic Class Vibe:
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
Concept | Description |
---|---|
Generic <T> | Type placeholder |
Function generic | function<T>(arg: T): T |
Class generic | class Box<T> { content: T } |
Benefit | Reusability, type safety, flexibility |
Bonus 👀
You can even use multiple types:
🧾 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:
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
🔍 Explanation:
-
The generic
<T extends { length: number }>
ensures that the passed argument has alength
property. -
getLength(123)
gives an error because numbers don’t havelength
.
✅ Example 2: Constraint with interface
🔍 Explanation:
-
The generic
T
must at least match the shape ofPerson
. -
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:
It’s like saying:
“Yeah, I’ll take you, as long as you’ve got these credentials.”
(Like a VIP pass 🎟️)
🔥 Slang Example:
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
Concept | Description |
---|---|
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. 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:
Here, T
can be any type: string
, number
, boolean
, custom objects, etc.
💻 2. Code Example + Explanation
✅ Basic Generic Function
🔍 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)
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.
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:
You just write one function using <T>
, and TypeScript figures out the rest.
✅ Summary
Term | Meaning |
---|---|
<T> | Generic type placeholder |
T in use | Becomes the real type when used |
Purpose | Reusability + Type Safety |
🧾 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:
Here, the generic T
is constrained — it must be a type that has a length
property.
💻 2. Code Example + Explanation
✅ Example 1: length
constraint
🔍 Explanation:
-
T extends { length: number }
: meansT
must have alength
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
🔍 Explanation:
-
T extends Person
meansT
must have at least the shape ofPerson
. -
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:
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
Feature | Description |
---|---|
extends in generic | Adds a constraint to the type |
Benefit | Better type safety, more control |
Common use | Enforce structure or required properties |
🧾 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:
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
🔍 Explanation:
-
identity<T>
: defines a generic function with type parameterT
. -
value: T
: parameter must be of typeT
. -
: T
: return type is alsoT
. -
When calling
identity<string>("Hello")
,T
is inferred asstring
. -
Same logic for
number
,boolean
, objects, etc.
✅ Example 2: Generic Function with Arrays
🔍 Explanation:
-
T[]
= array of some unknown typeT
. -
Returns the first element, and preserves its type.
-
So you get proper autocomplete & safety.
✅ Example 3: Function with Multiple Generics
🔍 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:
It’s saying:
“You give me a type, and I promise to stick with it.”
✅ Summary
Feature | Description |
---|---|
<T> | Generic type placeholder |
Generic Functions | Functions that work with any (typed) input |
Benefits | Type safety + code reusability |
🧾 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
🔍 Explanation:
-
Box<T>
is a generic interface with a property and method both usingT
. -
numberBox
usesBox<number>
, socontent
must be a number. -
stringBox
usesBox<string>
, socontent
must be a string.
✅ Generic Class
🔍 Explanation:
-
Storage<T>
is a generic class that stores items of typeT
. -
You create instances specifying the type:
Storage<number>
orStorage<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
Feature | Generic Interface | Generic Class |
---|---|---|
Purpose | Define flexible object shapes | Define flexible object blueprints |
Syntax | interface Name<T> {} | class Name<T> {} |
Type Safety | Ensures members use generic type T | Ensures properties and methods use T |
Usage | For typing objects | For creating reusable data structures |
🧾 1. Formal Definition
-
keyof T
is a TypeScript operator that produces a union of the keys (property names) of typeT
as string literal types. -
T[K]
is called indexed access type, which means the type of the propertyK
in typeT
. Here,K
must be a key ofT
.
Put simply:
-
keyof T
gives you all possible keys ofT
. -
T[K]
gives you the type of the value stored at keyK
inT
.
💻 2. Code Example + Explanation
🔍 Explanation:
-
keyof Person
creates a union type of all keys:"name" | "age" | "location"
. -
Person["name"]
accesses the type of thename
property, which isstring
. -
The function
getProperty
takes an objectobj
and a keykey
that must be one ofobj
’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:
-
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 isstring
.
In the function:
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
Operator | Meaning | Example |
---|---|---|
keyof T | Union of keys of type T | `"name" |
T[K] | Type of property K in type T | Person["name"] → string |
🧾 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:
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
🔍 Explanation:
-
ApiResponse<T>
is generic. -
We pass
Array<string>
as the type parameter, sodata
becomes an array of strings. -
This is a nested generic: generic inside a generic.
Conditional Types with Generics Example
🔍 Explanation:
-
T extends (...args: any[]) => infer R
checks ifT
is a function type. -
If yes,
ReturnTypeOrSelf<T>
resolves to the return typeR
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:
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
Concept | Description | Example |
---|---|---|
Nested Generics | Using generics inside other generics | ApiResponse<Array<string>> |
Conditional Types | Type logic with if-else style using generics | T extends U ? X : Y |
infer keyword | Extract a type from another type | infer R extracts function return |
🛠️ 6. Working with Modules & Namespaces
🧾 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
🔍 Explanation:
-
mathUtils.ts
exports multiple functions usingexport
. -
main.ts
imports them using destructured{ }
syntax.
✅ Default Export Example
🔍 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
😎 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
You choose exactly what you want:
🍕 Default Export = You get the whole pizza
You can call it anything:
🧪 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
Syntax | Purpose | Example |
---|---|---|
export const foo = ... | Named export | import { foo } from "./file" |
export default something | Default export | import anything from "./file" |
import { x } from ... | Import specific items | import { x } from "./mod" |
import x from ... | Import default export | import x from "./mod" |
import x, { y } from ... | Both default and named imports | import x, { y } from "./mod" |
🧾 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
🧠 Explanation:
-
export
sharesadd
andsubtract
frommath.ts
. -
In
main.ts
, we import them using{ }
.
✅ Default Export & Import
🧠 Explanation:
-
export default
marksgreet
as the primary export. -
You import it without curly braces.
✅ Mixed Export
🧠 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."
-
Default export: "This is the main tool."
📦 import
= “I’m grabbing tools from another toolbox.”
-
Grab named tools:
-
Grab the main (default) tool:
-
Grab both:
✅ Summary Table
Syntax | Purpose | Example |
---|---|---|
export const x = ... | Named export | import { x } from "./file" |
export default ... | Default export | import x from "./file" |
import { x } from ... | Named import | import { x } from "./file" |
import x from ... | Default import | import something from "./file" |
import x, { y } from ... | Default + named import | import def, { named } from "./file" |
🧾 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:
This was introduced in TypeScript 3.8.
💻 2. Code Example + Explanation
✅ Using import type
🧠 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 (norequire()
orimport
statements).
⚠ Without import type
(regular import)
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
)
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
Feature | Use | Pulls into runtime? |
---|---|---|
import type {...} | Just for type-checking | ❌ No |
import {...} | For runtime values & types | ✅ Yes |
🧾 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
🧠 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
🧠 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
🧠 Explanation:
-
namespace Status
merges into theenum 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:
Now
Player
has bothname
andlevel
. 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
Type | Merges? | Notes |
---|---|---|
interface | ✅ | Properties combine |
namespace | ✅ | Can merge with functions, enums, etc. |
type alias | ❌ | Cannot be merged |
class + namespace | ✅ | Add static utilities to classes |
🧾 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:
💻 2. Code Example + Explanation
🧩 Example 1: Declaring a global variable
Then in your code:
✅ 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
:
You can write a .d.ts
file:
Then in your TS file:
🧩 Example 3: Adding types to window or globalThis
In code:
😎 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
Feature | Description |
---|---|
.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 |
🧾 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:
💻 2. Code Examples + Explanation
🧩 Example 1: Declaring a global variable
In your TypeScript file:
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
Then you can use it:
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
Then use it in TS:
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 Case | Purpose |
---|---|
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️⃣ 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.
💻 Code Example:
Explanation:
-
GreetingProps
defines that the component expects aname
prop of typestring
. -
React.FC
(orReact.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:
Explanation:
-
useState<number>(0)
means this state will always be a number. -
Helps prevent you from accidentally passing a string like
"2"
tosetCount
.
😎 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:
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:
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 — noundefined
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:
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
Feature | Purpose | How TypeScript Helps |
---|---|---|
Props | Pass data into components | Defines the shape of props clearly |
State | Internal component data | Prevents type mismatch in useState |
Refs | Access DOM nodes or store mutable values | Ensures correct ref type (e.g., HTMLDivElement ) |
Context API | Global-like shared data | Enforces strict type of context values |
Custom Hooks | Reusable logic | Strong typing of args & return values |
🚀 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:
💻 2. Code Examples + Explanation:
📄 2.1. Pages with Typed Props (SSR)
Explanation:
-
HomeProps
ensures only valid props are passed to the page component. -
Next.js injects props via
getServerSideProps
.
📦 2.2. API Routes with Types
Explanation:
-
NextApiRequest
andNextApiResponse
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
Explanation:
-
AppProps
typesComponent
andpageProps
correctly for all pages.
🧭 2.4. Typed useRouter (Routing)
Pro Tip: To narrow id
to a string
, check type guards:
🎯 2.5. Dynamic Routes + Static Props
😎 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
Feature | TypeScript 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 |
🧾 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 withtsc
-
Typing Express handlers and middlewares
📦 Common types used:
-
Request
,Response
,NextFunction
fromexpress
-
RequestHandler
,Application
, etc.
💻 2. Code Example + Explanation
📁 Project Setup
Edit tsconfig.json
:
📝 Basic Express Server (with types)
🧠 Explanation:
-
✅
Request
,Response
,NextFunction
are typed fromexpress
. -
✅
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
This line:
means you can type:
-
URL params
-
Response body
-
Request body
-
Query string
⚙ Run the App
Or compile first:
😎 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
Feature | TypeScript 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 |
🧾 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:
💻 2. Code Example + Explanation
🧩 Example: Validating a POST request body in Express
🧠 Code Explanation:
What? | Why? |
---|---|
z.object({...}) | Creates a Zod schema for validation |
safeParse(req.body) | Parses + validates safely; no throw |
parsed.success | Boolean — tells if validation passed |
parsed.data | The valid, type-safe data |
z.infer<typeof Schema> | Generates a fully accurate TS type from the schema automatically |
✅ 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
Validator | Use 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
Feature | Benefit |
---|---|
Define + validate schema | In one place |
TypeScript-aware | Inferred types from schema |
Clean error handling | .safeParse() avoids throwing |
Works great in Express | Use in route handlers for safe req.body |
Powerful + composable | Nesting, merging, reusing schemas |
🧾 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
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
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
Feature | Axios | Fetch |
---|---|---|
How to type response | axios.get<User>(url) | const data: User = await response.json() |
Error handling | Try/catch, Axios throws on bad status | Manually check response.ok |
Ease of use | Auto-parses JSON, built-in interceptors | Manual JSON parsing |
Typing request body | axios.post<ReturnType, AxiosResponse<ReturnType>>(url, body) | Manual JSON.stringify + typing |
🧠 8. TypeScript in Real-world Apps
✅ Redux (with Redux Toolkit)
1. Define your state slice type
2. Create slice with typed state
3. Setup typed RootState
4. Use typed selectors and dispatch
✅ Zustand
1. Define state and actions types
2. Create store with full typing
3. Use store in components
TL;DR
Framework | Where to Type | How |
---|---|---|
Redux | RootState , initialState , reducers | Use ReturnType and configureStore |
Zustand | Store creation function | Pass generic to create<StoreType>() |
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
Usage with Fetch / Axios
✅ 2. Typing API Request
Example: POST /login
Usage:
✅ 3. Typing API in Redux (RTK Query)
If you're using RTK Query, it's built for strong typing.
✅ 4. Typing API in Zustand
✅ 5. Centralize API Types (Best Practice)
Put all request/response types in a file like:
Example:
When handling form data with TypeScript, the key is to strictly type:
-
The form state
-
The input handlers
-
The submit function
Here's how to handle it properly in different ways:
✅ 1. Typing Form Data Structure
✅ 2. Controlled Components (React)
Minimal Example:
✅ 3. Using Refs (Less common, but valid)
✅ 4. With Libraries (React Hook Form)
✅ 5. Type Validation
You can combine it with zod
, yup
, or joi
for schema-based validation.
TL;DR
Task | Type With |
---|---|
useState | Interface for form shape |
onChange | React.ChangeEvent<HTMLInputElement> |
onSubmit | React.FormEvent<HTMLFormElement> |
Schema Validation | zod , yup , or react-hook-form |
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 ❌)
✅ 2. Correct Way: Use Type Guard ✅
✅ 3. Handling Axios or Custom Error Types
✅ 4. With Custom Error Class
✅ 5. Inline Type Guard (Quick + Clean)
TL;DR
Case | How 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 |
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)
✅ 2. Best Practices
📦 Organize by Feature, not by Type
Instead of components/
, reducers/
, actions/
— group by feature domain:
🧱 Use Absolute Imports
Use a tsconfig.json
path alias:
🧪 Testing Structure
Use Jest, Vitest, or Playwright for testing.
🌐 API Handling Pattern
🎯 Type First Development
-
Create
types.ts
orauthTypes.ts
inside each feature folder. -
Always type API request & response.
🔒 Environment Config
Use .env
, .env.local
, etc.
Create a wrapper:
🗃️ Redux / Zustand Store Best Practice
-
Create per-feature slices
-
Global store should just register them
-
Split selectors & actions if complex
✅ 3. File Naming Conventions
Type | Naming |
---|---|
Component | UserCard.tsx |
Slice/API | userSlice.ts |
Hook | useUser.ts |
Type | userTypes.ts |
Utils | formatDate.ts |
-
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
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. never
— Type that should not exist
✅ Definition:
A type that never happens. Used for:
-
Unreachable code
-
Exhaustive checks
🔍 Example:
✅ TL;DR:
Use never
to enforce exhaustive checks and signal impossible states.
🔥 2. unknown
— Safe alternative to any
✅ Definition:
unknown
means "we don't know the type yet", but forces type narrowing before usage.
🔍 Example:
✅ Use Case:
-
API responses
-
Generic utilities
-
Safer than
any
🔥 3. infer
— Type extraction within conditional types
✅ Definition:
Used in conditional types to pull out a type from a structure.
🔍 Example 1: Extract array element type
🔍 Example 2: Extract return type of a function
✅ Use Case:
-
Writing utilities like
ReturnType<T>
-
Creating powerful reusable types
🔥 4. as const
— Literal type inference
🔍 Without:
🔍 With:
Now you can create union types from it:
🔥 5. keyof
, typeof
, in
, extends
keyof
— Union of keys:
typeof
— Get type from variable:
in
— Map over keys:
extends
— Conditionals:
🔥 6. Discriminated Unions + never
check
TL;DR Table
Keyword | Use Case | Key Insight |
---|---|---|
never | Exhaustive checks, impossible paths | Should never be reached |
unknown | Safer alternative to any | Forces type checking |
infer | Extract inner types in conditional types | Used in reusable type utilities |
as const | Preserve literal types | Enables typeof array[number] tricks |
keyof | Get object keys as union | Enables mapped types |
in | Iterate over keys | Used in mapped type definitions |
extends | Type conditionals | Like if/else at the type level |
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
🔍 Use Case:
Ensure generic types have required shape.
✅ 2. Default Generic Types
🔍 Use Case:
Make generics optional while maintaining flexibility.
✅ 3. Generic Functions With Multiple Type Params
🔍 Use Case:
Combine objects while preserving both types.
✅ 4. Conditional Types + Generics
✅ 5. Infer With Generics
🔍 Use Case:
Extract internal types (e.g., for custom ReturnType, Awaited, etc.)
✅ 6. Generic Interfaces
✅ 7. Generic React Hook
✅ 8. Mapped Generics + Utility Types
✅ 9. Generic Component Props
✅ 10. Generic Error Wrapper Utility
TL;DR Cheatsheet
Feature | Syntax | Purpose |
---|---|---|
Constraint | <T extends SomeType> | Limit what types T can be |
Default | <T = DefaultType> | Use fallback when not provided |
Inference | infer U | Pull out a sub-type from a structure |
Conditional Types | T extends U ? X : Y | Type-level branching |
Mapped Types | { [K in keyof T]: T[K] } | Transform or remap object keys |
Utility Types | Partial<T>, Pick<T>, Omit<T> | Base templates for reuse |
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
📌 Use when: You’re doing patch/update forms, or building draft-like state.
🛠️ 2. Required<T>
Makes all properties mandatory
📌 Use when: You want to force full completion (e.g., finalizing drafts, schemas).
🛠️ 3. Readonly<T>
Locks object properties
📌 Use when: You want immutability, like in Zustand/Redux or constants.
🛠️ 4. Pick<T, K>
Select only specific keys
📌 Use when: You want limited view models (e.g. list view, dropdowns).
🛠️ 5. Omit<T, K>
Exclude specific keys
📌 Use when: You're hiding sensitive fields, or creating simplified versions.
🛠️ 6. Record<K, T>
Typed object maps
📌 Use when: You’re building role maps, config dictionaries, or enum-based objects.
🛠️ 7. Exclude<T, U>
Removes from union
📌 Use when: You want to filter enums or unions based on context.
🛠️ 8. Extract<T, U>
Keeps only matching union types
📌 Use when: You want to narrow down types to a specific subset.
🛠️ 9. ReturnType<T>
Gets return type of a function
📌 Use when: You want to avoid manual duplication of output types.
🛠️ 10. NonNullable<T>
Remove
null
andundefined
from a type
📌 Use when: You’re sure the value is present after validation.
🛠️ 11. Parameters<T>
Gets function’s parameter types as a tuple
📌 Use when: You’re reusing argument types in wrappers or middleware.
TL;DR – Pick the Right Tool
Utility | Use When You... |
---|---|
Partial | Want optional form fields or patch updates |
Required | Want to enforce complete object structure |
Readonly | Need immutability or frozen configs |
Pick | Want a smaller view of a type |
Omit | Want to hide/remove keys |
Record | Map keys to typed values |
Exclude | Filter out values from a union |
Extract | Keep only certain union values |
ReturnType | Reuse function outputs |
NonNullable | Eliminate null /undefined for safe usage |
Parameters | Extract argument types for reuse/wrapping |
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
✅ Both are structurally identical here. You can use them interchangeably for objects.
⚔️ 2. Key Differences
Feature | interface | type |
---|---|---|
Extending | ✅ Can extend other interfaces | ✅ Can extend via intersection |
Merging | ✅ Declaration 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)
🔸 This is great for 3rd-party libraries or plugin patterns.
🔀 4. Unions & Intersections (type only)
Interfaces can't do this — only types can.
🔧 5. Mapped & Conditional Types (type only)
✅ type
is more powerful for type-level programming.
🧩 6. Extending
Interface:
Type:
Both work — interfaces use extends
, types use &
.
🧠 7. Best Practices (When to Use What)
Use Case | Use interface ✅ | Use 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
Criteria | interface | type |
---|---|---|
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.
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.
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
).
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.
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.
Use string unions when:
-
You need type safety without runtime overhead.
-
You want flexibility in type compositions.
4. union
vs intersection
-
Union (
|
) — use when a value is one of multiple types: -
Intersection (
&
) — use when a type combines multiple structures:
5. never
vs unknown
vs any
-
never
— Use for impossible states, exhaustive checks: -
unknown
— Use when type is unknown but must be narrowed before usage: -
any
— Avoid unless dealing with dynamic 3rd-party data or gradual migration.
6. infer
& Conditional Types
-
Use
infer
when extracting types dynamically, like ReturnType: -
Use conditional types for type-level logic:
7. Utility Types
-
Use
Partial<T>
when building update forms or PATCH requests. -
Use
Pick<T, K>
orOmit<T, K>
for selective data exposure. -
Use
Readonly<T>
for immutability in state stores (Redux/Zustand).
Example:
8. Generics
-
Use generics when your function/type must be reusable with different types:
-
Use constraints (
<T extends Something>
) to restrict:
9. Discriminated Unions + never
Use this for exhaustive checks:
10. When to Combine Features
-
React props → Use
interface
ortype
withPick
/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/contracts →
interface
-
Unions, primitives, tuples, conditionals →
type
-
Reusable code with type inference → Generics +
infer
-
Immutability or transformations → Utility types (
Readonly
,Omit
, etc.) -
Validation & exhaustive checks →
never
, discriminated unions.
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:
-
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:
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.
5. Convert default exports and imports
Make sure your import/export syntax matches TS expectations, often:
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:
But prefer narrowing with if (input instanceof HTMLInputElement)
.
9. Introduce interfaces for props and state in React
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
ortypescript-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 preferunknown
-
Leverage
@types
for libraries -
Refactor step-by-step, keep the app working
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
-
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).
-
-
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.
-
-
Declaration File Generation (
.d.ts
files):-
Optionally generates declaration files to expose types to consumers, useful for libraries.
-
-
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
Aspect | Details |
---|---|
Runtime Performance | No direct improvement by TS |
Developer Efficiency | Significant gains in bug prevention & refactoring confidence |
Compilation Steps | Parsing → Type Checking → Transpiling to JS |
Output | Clean JS, no types, target configurable |
Build Optimization | Done by bundlers/minifiers, not TS compiler |
1. API Contract Design
-
Define shared types/interfaces for request/response bodies between backend & frontend.
-
Use tools like
tsc
oropenapi-typescript
to generate types from API specs. -
This prevents mismatches, enabling IDE autocomplete and compile-time validation.
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.
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.
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.
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.
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.
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 Aspect | TS Role | Benefit |
---|---|---|
API Contract | Shared request/response types | Prevent mismatches |
State Management | Typed state/actions | Predictability & bug reduction |
Domain Modeling | Interfaces, generics, validation | Robust, refactor-friendly design |
UI Components | Strong prop & hook typing | Safe reusable UI |
Error Handling | Discriminated unions | Predictable failures |
Middleware | Generic pipeline types | Composability & type safety |
Code Generation | Auto-generate types & clients | Sync and consistency |
Comments
Post a Comment