TypeScript Mastery Guide
Complete guide from basics to advanced type system
The Complete TypeScript Mastery Guide
Master static typing, advanced types, and modern JavaScript development with TypeScript's powerful type system
Understanding TypeScript: JavaScript With Superpowers
TypeScript is a strongly typed programming language that builds on JavaScript, developed and maintained by Microsoft. It adds static type definitions to JavaScript, enabling developers to catch errors during development rather than at runtime.
What makes TypeScript powerful is its type system that can express complex relationships while maintaining full compatibility with existing JavaScript code. It compiles to clean, readable JavaScript that runs anywhere JavaScript runs.
Industry Standard: TypeScript is used by companies like Microsoft, Google, Airbnb, and Slack. Its adoption has grown rapidly due to improved developer experience, better tooling, and reduced runtime errors in large-scale applications.
1. TypeScript Basics & Types
Getting Started: Your First TypeScript Program
// Install TypeScript globally
// npm install -g typescript
// Create a TypeScript file: hello.ts
function greet(name: string): string {
return `Hello, ${name}!`;
}
const message: string = greet("TypeScript");
console.log(message);
// Compile to JavaScript
// tsc hello.ts
// This generates hello.js
// Basic type annotations
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let tuple: [string, number] = ["hello", 10];
// Any type (opt-out of type checking)
let notSure: any = 4;
notSure = "maybe a string";
notSure = false;
// Void type (no return value)
function warnUser(): void {
console.log("This is a warning");
}
// Null and Undefined
let u: undefined = undefined;
let n: null = null;
// Never type (functions that never return)
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
// Type assertions (type casting)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
// Alternative syntax:
// let strLength: number = (<string>someValue).length;Basic Types and Type Annotations
// Boolean
let isActive: boolean = true;
let isCompleted: boolean = false;
// Number (all floating point)
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
// String
let color: string = "blue";
color = 'red';
let fullName: string = `Bob Smith`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName} and I'm ${age} years old.`;
// Array
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // Generic array type
let mixed: (string | number)[] = [1, "hello", 2, "world"];
// Tuple (fixed-length array with known types)
let tuple1: [string, number] = ["hello", 10];
// tuple1 = [10, "hello"]; // Error: wrong types
// tuple1[3] = "world"; // Error: index out of bounds
// Enum (named constants)
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green;
enum ColorWithValues {
Red = 1,
Green = 2,
Blue = 4
}
enum StringEnum {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
// Object type
let obj: object = {};
let person: { name: string; age: number } = {
name: "John",
age: 30
};
// Optional properties
let optionalPerson: {
name: string;
age?: number; // Optional property
} = {
name: "Jane"
};
// Readonly properties
let readonlyPerson: {
readonly name: string;
readonly age: number;
} = {
name: "Bob",
age: 25
};
// readonlyPerson.name = "Alice"; // Error: readonly
// Index signatures
let dictionary: { [key: string]: number } = {
"apples": 10,
"bananas": 5
};
// Type aliases
type Person = {
name: string;
age: number;
email?: string;
};
let user: Person = {
name: "Alice",
age: 30
};
// Union types
let id: string | number = "abc123";
id = 123; // Also valid
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}
// Intersection types
type Employee = Person & {
employeeId: string;
department: string;
};
let employee: Employee = {
name: "Bob",
age: 35,
employeeId: "E123",
department: "Engineering"
};Interfaces: Defining Object Shapes
// Basic interface
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Readonly property
}
// Using the interface
let user1: User = {
id: 1,
name: "John Doe",
email: "john@example.com",
createdAt: new Date()
};
// user1.createdAt = new Date(); // Error: readonly
// Interface with function type
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(src: string, sub: string): boolean {
return src.search(sub) > -1;
};
// Interface with index signature
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// Interface extending another interface
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
let myDog: Dog = {
name: "Buddy",
age: 3,
breed: "Golden Retriever",
bark: () => console.log("Woof!")
};
// Interface extending multiple interfaces
interface Employee {
employeeId: string;
department: string;
}
interface Manager extends Person, Employee {
teamSize: number;
manage(): void;
}
// Interface for class implementation
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
// Hybrid types (object that works as function and object)
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = function (start: number) {} as Counter;
counter.interval = 123;
counter.reset = function () {};
return counter;
}
// Interface vs Type Alias
// Interfaces can be extended and implemented, type aliases can use unions and intersections
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle;
function area(shape: Shape): number {
switch (shape.kind) {
case "square":
return shape.size * shape.size;
case "rectangle":
return shape.width * shape.height;
}
}
// Declaration merging (interfaces only)
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = { height: 5, width: 6, scale: 10 };Type Inference and Type Compatibility
// Type inference
let x = 3; // TypeScript infers number
let y = "hello"; // TypeScript infers string
let z = [1, 2, 3]; // TypeScript infers number[]
// Contextual typing
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); // TypeScript knows mouseEvent is MouseEvent
};
// Best common type inference
let values = [0, 1, null]; // TypeScript infers (number | null)[]
// Type compatibility
interface Named {
name: string;
}
class Person {
name: string;
age: number;
}
let p: Named;
p = new Person(); // OK, because Person has name property
// Function type compatibility
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
// x = y; // Error: y requires two parameters
// Optional parameters and rest parameters
function invokeLater(args: any[], callback: (...args: any[]) => void) {
callback(...args);
}
invokeLater([1, 2], (x, y) => console.log(x + y));
// Enum compatibility
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
// status = Color.Red; // Error: different enum types
// Class compatibility
class Animal {
feet: number;
constructor(name: string, numFeet: number) {}
}
class Size {
feet: number;
constructor(numFeet: number) {}
}
let a: Animal = new Animal("dog", 4);
let s: Size = new Size(4);
a = s; // OK
s = a; // OK
// Generic type compatibility
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string>;
x = y; // OK, because structure is the same
interface NotEmpty<T> {
data: T;
}
let x2: NotEmpty<number>;
let y2: NotEmpty<string>;
// x2 = y2; // Error: different generic types
// Advanced type inference with conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// Mapped types
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface Point {
x: number;
y: number;
}
type ReadonlyPoint = Readonly<Point>;
// Equivalent to: { readonly x: number; readonly y: number; }
// Keyof operator
type PointKeys = keyof Point; // "x" | "y"
// Typeof operator
function getPoint(): Point {
return { x: 1, y: 2 };
}
type PointType = typeof getPoint; // () => Point
type ReturnType = ReturnType<typeof getPoint>; // Point2. Advanced Types
Union and Intersection Types
// Union types
type ID = string | number;
type Status = "active" | "inactive" | "pending";
function processID(id: ID): string {
if (typeof id === "string") {
return id.toUpperCase();
} else {
return id.toString();
}
}
// Discriminated unions (tagged unions)
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
type Shape = Circle | Square | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// Exhaustiveness checking
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
// Intersection types
interface BusinessPartner {
name: string;
credit: number;
}
interface Identity {
id: string;
name: string;
}
interface Contact {
email: string;
phone: string;
}
type Employee = Identity & Contact;
type Customer = BusinessPartner & Contact;
// Mixins with intersection types
type BusinessEmployee = BusinessPartner & Employee;
// Conditional types
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>; // true
type B = IsArray<string>; // false
// Extract and Exclude utility types
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
// NonNullable utility type
type T2 = NonNullable<string | number | undefined>; // string | number
// Conditional type with infer
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type ItemType = ArrayElementType<number[]>; // number
type ItemType2 = ArrayElementType<string[]>; // string
// Function return type inference
type FunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type PromiseType<T> = T extends Promise<infer U> ? U : never;
// Recursive conditional types
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface UserProfile {
name: string;
settings: {
theme: string;
notifications: boolean;
};
}
type ReadonlyUserProfile = DeepReadonly<UserProfile>;
// Template literal types
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
// "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
// Mapped types with template literals
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// getLocation: () => string;
// }Utility Types and Type Manipulation
// Partial<T> - Make all properties optional
interface Todo {
title: string;
description: string;
completed: boolean;
}
type PartialTodo = Partial<Todo>;
// Equivalent to:
// {
// title?: string;
// description?: string;
// completed?: boolean;
// }
// Required<T> - Make all properties required
type RequiredTodo = Required<PartialTodo>;
// Readonly<T> - Make all properties readonly
type ReadonlyTodo = Readonly<Todo>;
// Record<K, T> - Construct object type with property keys K and type T
type PageInfo = {
title: string;
};
type Page = "home" | "about" | "contact";
const navigation: Record<Page, PageInfo> = {
home: { title: "Home" },
about: { title: "About" },
contact: { title: "Contact" }
};
// Pick<T, K> - Pick set of properties K from T
type TodoPreview = Pick<Todo, "title" | "completed">;
// { title: string; completed: boolean; }
// Omit<T, K> - Construct type by omitting properties K from T
type TodoInfo = Omit<Todo, "completed">;
// { title: string; description: string; }
// Exclude<T, U> - Exclude from T those types that are assignable to U
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<string | number | (() => void), Function>; // string | number
// Extract<T, U> - Extract from T those types that are assignable to U
type T2 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T3 = Extract<string | number | (() => void), Function>; // () => void
// NonNullable<T> - Exclude null and undefined from T
type T4 = NonNullable<string | number | undefined>; // string | number
type T5 = NonNullable<string[] | null | undefined>; // string[]
// Parameters<T> - Obtain parameters of a function type
type T6 = Parameters<(s: string, n: number) => void>; // [string, number]
// ConstructorParameters<T> - Obtain parameters of a constructor function type
type T7 = ConstructorParameters<ErrorConstructor>; // [string?]
// ReturnType<T> - Obtain return type of a function type
type T8 = ReturnType<() => string>; // string
// InstanceType<T> - Obtain instance type of a constructor function type
type T9 = InstanceType<ErrorConstructor>; // Error
// ThisType<T> - Marker for contextual 'this' type
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>;
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
let data: object = desc.data || {};
let methods: object = desc.methods || {};
return { ...data, ...methods } as D & M;
}
let obj = makeObject({
data: { x: 0, y: 0 },
methods: {
moveBy(dx: number, dy: number) {
this.x += dx; // Strongly typed this
this.y += dy; // Strongly typed this
}
}
});
// Uppercase, Lowercase, Capitalize, Uncapitalize
type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>; // "HELLO, WORLD"
type QuietGreeting = Lowercase<Greeting>; // "hello, world"
type CapitalizedGreeting = Capitalize<Greeting>; // "Hello, world"
type UncapitalizedGreeting = Uncapitalize<Greeting>; // "hello, world"
// Custom utility types
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
type DeepMutable<T> = {
-readonly [P in keyof T]: T[P] extends object ? DeepMutable<T[P]> : T[P];
};
// Branded types for nominal typing
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;
function createUserId(id: string): UserId {
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
let userId: UserId = createUserId("user-123");
let productId: ProductId = createProductId("product-456");
// userId = productId; // Error: different brands3. Functions & Classes
Function Types and Overloads
// Basic function types
function add(x: number, y: number): number {
return x + y;
}
// Function type expressions
type AddFunction = (x: number, y: number) => number;
const add2: AddFunction = (x, y) => x + y;
// Optional and default parameters
function buildName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName;
}
function buildName2(firstName: string, lastName = "Smith"): string {
return `${firstName} ${lastName}`;
}
// Rest parameters
function buildName3(firstName: string, ...restOfName: string[]): string {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName3("Joseph", "Samuel", "Lucas", "MacKinzie");
// Function overloading
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 2023);
// const d3 = makeDate(1, 3); // Error: no overload expects 2 arguments
// This parameter
interface User {
id: number;
admin: boolean;
becomeAdmin: () => void;
}
const user: User = {
id: 123,
admin: false,
becomeAdmin: function () {
this.admin = true;
}
};
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
// Generic functions
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0];
}
// s is of type 'string'
const s = firstElement(["a", "b", "c"]);
// n is of type 'number'
const n = firstElement([1, 2, 3]);
// Multiple type parameters
function map<Input, Output>(
arr: Input[],
func: (arg: Input) => Output
): Output[] {
return arr.map(func);
}
// Parameter 'n' is of type 'string'
// 'parsed' is of type 'number[]'
const parsed = map(["1", "2", "3"], (n) => parseInt(n));
// Constraints on generic types
function longest<Type extends { length: number }>(a: Type, b: Type): Type {
if (a.length >= b.length) {
return a;
} else {
return b;
}
}
// longerArray is of type 'number[]'
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString is of type 'alice' | 'bob'
const longerString = longest("alice", "bob");
// Error! Numbers don't have a 'length' property
// const notOK = longest(10, 100);
// Specifying type arguments
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2);
}
// const arr = combine([1, 2, 3], ["hello"]); // Error!
const arr = combine<string | number>([1, 2, 3], ["hello"]); // OK
// Function type inference
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
// Call signatures
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
// Construct signatures
type SomeConstructor = {
new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
return new ctor("hello");
}Classes and Object-Oriented Programming
// Basic class
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
// Field declarations
class PointWithDefaults {
x = 0;
y = 0;
}
// readonly fields
class Greeter {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
// Access modifiers: public, private, protected
class Animal {
public name: string;
private age: number;
protected species: string;
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
public getName(): string {
return this.name;
}
private getAge(): number {
return this.age;
}
protected getSpecies(): string {
return this.species;
}
}
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age, "Canine");
}
public getInfo(): string {
return `${this.name} is a ${this.getSpecies()}`; // Can access protected
// return this.getAge(); // Error: private method
}
}
// Parameter properties (shorthand)
class PointWithParams {
constructor(public x: number, public y: number, private z: number) {}
}
// Getters and setters
class Temperature {
private _celsius: number = 0;
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature below absolute zero!");
}
this._celsius = value;
}
get fahrenheit(): number {
return this._celsius * 1.8 + 32;
}
set fahrenheit(value: number) {
this._celsius = (value - 32) / 1.8;
}
}
// Static properties and methods
class MyClass {
static staticProperty = "hello";
static staticMethod() {
return "world";
}
}
console.log(MyClass.staticProperty); // "hello"
console.log(MyClass.staticMethod()); // "world"
// Abstract classes
abstract class Department {
constructor(public name: string) {}
abstract describe(): void;
printName(): void {
console.log("Department name: " + this.name);
}
}
class AccountingDepartment extends Department {
describe(): void {
console.log("Accounting Department - " + this.name);
}
}
// Cannot create instance of abstract class
// const dept = new Department(); // Error!
const accounting = new AccountingDepartment("Accounting");
// Implementing interfaces
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
// Generic classes
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
constructor(zeroValue: NumType, add: (x: NumType, y: NumType) => NumType) {
this.zeroValue = zeroValue;
this.add = add;
}
}
const myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
// Class expressions
const MyClassExpression = class<T> {
content: T;
constructor(value: T) {
this.content = value;
}
};
const myInstance = new MyClassExpression("hello");
// this-based type guards
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
}
class FileRep extends FileSystemObject {
constructor(public path: string, private content: string) {
super();
}
}
class Directory extends FileSystemObject {
children: FileSystemObject[];
constructor() {
super();
this.children = [];
}
}
function processObject(obj: FileSystemObject) {
if (obj.isFile()) {
// obj is FileRep
console.log(obj.path);
} else if (obj.isDirectory()) {
// obj is Directory
console.log(obj.children);
}
}4. Generics & Utility Types
Generic Constraints and Advanced Patterns
// Basic generic constraints
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength([1, 2, 3]); // OK
logLength("hello"); // OK
// logLength(42); // Error: number doesn't have length
// Using type parameters in generic constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let person = { name: "John", age: 30 };
let name = getProperty(person, "name"); // string
let age = getProperty(person, "age"); // number
// let unknown = getProperty(person, "unknown"); // Error
// Generic classes with constraints
class GenericCollection<T extends { id: number }> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getById(id: number): T | undefined {
return this.items.find(item => item.id === id);
}
}
interface User {
id: number;
name: string;
}
const userCollection = new GenericCollection<User>();
userCollection.add({ id: 1, name: "John" });
// Generic defaults
interface ApiResponse<T = any> {
data: T;
status: number;
message: string;
}
const stringResponse: ApiResponse<string> = {
data: "hello",
status: 200,
message: "OK"
};
const defaultResponse: ApiResponse = {
data: { anything: "goes" },
status: 200,
message: "OK"
};
// Conditional types with generics
type IsString<T> = T extends string ? true : false;
type MessageType<T> = T extends { message: string } ? T["message"] : never;
interface Email {
message: string;
subject: string;
}
type EmailMessage = MessageType<Email>; // string
// Mapped types with generics
type OptionalFields<T, K extends keyof T> = {
[P in K]?: T[P];
} & Pick<T, Exclude<keyof T, K>>;
interface Product {
id: number;
name: string;
price: number;
description: string;
}
type ProductPreview = OptionalFields<Product, "description" | "price">;
// { id: number; name: string; price?: number; description?: string; }
// Generic conditional types
type Flatten<T> = T extends any[] ? T[number] : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Infer keyword in conditional types
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Num2 = GetReturnType<() => number>; // number
type Str2 = GetReturnType<(x: string) => string>; // string
// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
// Non-distributive version
type ToArrayNonDistributive<T> = [T] extends [any] ? T[] : never;
type StrOrNumArr = ToArrayNonDistributive<string | number>; // (string | number)[]
// Template literal types with generics
type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;
type T0 = EventName<"foo">; // "fooChanged"
type T1 = Concat<"Hello", "World">; // "HelloWorld"
// Recursive generic types
type Json =
| string
| number
| boolean
| null
| { [key: string]: Json }
| Json[];
const jsonData: Json = {
name: "John",
age: 30,
hobbies: ["reading", "gaming"],
address: {
street: "123 Main St",
city: "Anytown"
}
};
// Generic constraints with keyof
function updateObject<T extends object, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): T {
return { ...obj, [key]: value };
}
const updatedPerson = updateObject(person, "name", "Jane");
// Generic utility type creation
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
// Branded types for nominal typing
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, "UserId">;
type ProductId = Brand<string, "ProductId">;
function createUserId(id: string): UserId {
return id as UserId;
}
function createProductId(id: string): ProductId {
return id as ProductId;
}
let userId: UserId = createUserId("user-123");
let productId: ProductId = createProductId("product-456");
// userId = productId; // Error: different brandsAdvanced Generic Patterns
// Higher-order generic functions
function compose<A, B, C>(
f: (a: A) => B,
g: (b: B) => C
): (a: A) => C {
return (a: A) => g(f(a));
}
function length(s: string): number {
return s.length;
}
function double(n: number): number {
return n * 2;
}
const stringToDoubleLength = compose(length, double);
const result = stringToDoubleLength("hello"); // 10
// Generic interface with multiple type parameters
interface KeyValuePair<K, V> {
key: K;
value: V;
}
class Dictionary<K extends string | number, V> {
private items: KeyValuePair<K, V>[] = [];
set(key: K, value: V): void {
const index = this.items.findIndex(item => item.key === key);
if (index >= 0) {
this.items[index].value = value;
} else {
this.items.push({ key, value });
}
}
get(key: K): V | undefined {
return this.items.find(item => item.key === key)?.value;
}
}
const stringDict = new Dictionary<string, number>();
stringDict.set("age", 30);
stringDict.set("score", 95);
// Conditional type chains
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<() => void>; // "function"
type T2 = TypeName<number[]>; // "object"
// Extract promise types recursively
type Awaited<T> =
T extends PromiseLike<infer U> ? Awaited<U> : T;
type P1 = Awaited<Promise<string>>; // string
type P2 = Awaited<Promise<Promise<number>>>; // number
type P3 = Awaited<Promise<string | Promise<number>>>; // string | number
// Union to intersection
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never;
type Result = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }
// String manipulation types
type GetRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | GetRouteParams<`${Rest}`>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = GetRouteParams<"/users/:id/posts/:postId">; // "id" | "postId"
// Function composition types
type FunctionType = (...args: any[]) => any;
type Compose<F extends FunctionType[]> =
F extends [infer First, ...infer Rest]
? First extends FunctionType
? Rest extends FunctionType[]
? (input: Parameters<First>[0]) => ComposeReturn<Rest, ReturnType<First>>
: never
: never
: (input: any) => any;
type ComposeReturn<F extends FunctionType[], Input> =
F extends [infer First, ...infer Rest]
? First extends FunctionType
? Rest extends FunctionType[]
? ComposeReturn<Rest, ReturnType<First>>
: ReturnType<First>
: Input
: Input;
// Generic type guards
function isStringArray(value: any): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
function processValue(value: unknown): void {
if (isStringArray(value)) {
// value is string[] here
value.map(str => str.toUpperCase());
}
}
// Generic constructor types
interface Constructor<T> {
new(...args: any[]): T;
}
function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
return new ctor(...args);
}
class Person {
constructor(public name: string) {}
}
const personInstance = createInstance(Person, "John");
// Mapped types with conditional modifiers
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type CompleteObject<T> = T & Getters<T> & Setters<T>;
interface UserData {
name: string;
age: number;
}
type CompleteUser = CompleteObject<UserData>;
// {
// name: string;
// age: number;
// getName: () => string;
// getAge: () => number;
// setName: (value: string) => void;
// setAge: (value: number) => void;
// }
// Recursive type constraints
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepReadonly<T[P]>
: T[P];
};
type DeepMutable<T> = {
-readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P]
: DeepMutable<T[P]>
: T[P];
};💻 TypeScript Practice Projects
Beginner Level
- 1Build a Type-Safe Calculator with proper function types
- 2Create a User Management System with interfaces
- 3Implement a Shopping Cart with type-safe operations
- 4Build a Form Validation Library with TypeScript types
- 5Create a Todo App with proper type definitions
Intermediate Level
- 1Develop a Generic Data Fetching Hook with error handling
- 2Build a Type-Safe Router with path parameter extraction
- 3Create a Validation Library with Zod-like type inference
- 4Implement a State Management System with TypeScript
- 5Build a Generic API Client with type-safe endpoints
Advanced Level
- 1Create a Type-Safe SQL Query Builder
- 2Build a Generic Form Builder with TypeScript
- 3Implement a Type-Safe Event Emitter System
- 4Develop a Generic Cache System with type safety
- 5Build a Type-Safe Plugin System for a framework
📋 TypeScript Quick Reference
Essential Types
- •string, number, boolean - Primitives
- •any, unknown, never - Special types
- •Array<T>, Tuple - Collections
- •interface, type - Object shapes
- •union |, intersection & - Type combinations
- •generic <T> - Reusable types
- •keyof, typeof - Type operators
Utility Types
- •Partial<T> - Make all optional
- •Required<T> - Make all required
- •Readonly<T> - Make all readonly
- •Pick<T, K> - Pick properties
- •Omit<T, K> - Omit properties
- •ReturnType<T> - Function return type
- •Parameters<T> - Function parameters
Master Type-Safe JavaScript Development!
TypeScript brings the power of static typing to JavaScript, enabling you to catch errors early, write more maintainable code, and scale your applications with confidence. Its rich type system and excellent tooling make it an essential skill for modern web development.
With TypeScript, you get the flexibility of JavaScript combined with the safety of static types, making it perfect for large-scale applications, team collaborations, and long-term project maintenance.