All topics
General · Learning hub

TypeScript notes for developers

Master TypeScript with a curated set of 16 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
TypeScript

Type System & Annotations

TypeScript Type System & Annotations TypeScript extends JavaScript by adding static type definitions. Types provide a way to describe the shape of objects, enab

TypeScript Type System & Annotations

TypeScript extends JavaScript by adding static type definitions. Types provide a way to describe the shape of objects, enabling better documentation and error detection.

Basic Types

// Primitives
let name: string = 'John';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// BigInt and Symbol
let bigNumber: bigint = 100n;
let uniqueId: symbol = Symbol('id');

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// Tuples - fixed length and types
let tuple: [string, number] = ['age', 30];
let RGB: [number, number, number] = [255, 0, 0];

// Tuple with optional and rest
let optional: [string, number?] = ['hello'];
let rest: [string, ...number[]] = ['numbers', 1, 2, 3];

// Objects
let user: { name: string; age: number } = {
  name: 'John',
  age: 30
};

// Optional properties
let person: { name: string; age?: number } = { name: 'Jane' };

// Readonly properties
let readonlyUser: { readonly id: number; name: string } = {
  id: 1,
  name: 'John'
};
// readonlyUser.id = 2;  // Error!

// Index signatures
interface StringMap {
  [key: string]: string;
}

const map: StringMap = {
  name: 'John',
  email: 'john@example.com'
};

Function Types

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

// Arrow functions
const multiply = (a: number, b: number): number => a * b;

// Optional parameters
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}`;
}

// Default parameters
function createUser(name: string, age: number = 18): User {
  return { name, age };
}

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

// Function type
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

// Void return type
function log(message: string): void {
  console.log(message);
}

// Never return type (never returns)
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

// Function overloads
function getValue(value: string): string;
function getValue(value: number): number;
function getValue(value: boolean): boolean;
function getValue(value: string | number | boolean): string | number | boolean {
  return value;
}

const str = getValue('hello');  // Type: string
const num = getValue(42);       // Type: number

Union & Intersection Types

// Union types (OR)
let id: string | number;
id = 'abc';  // ✅
id = 123;    // ✅
// id = true;   // ❌ Error

type Status = 'pending' | 'active' | 'completed';
let status: Status = 'pending';  // ✅
// status = 'invalid';  // ❌ Error

// Intersection types (AND)
type Person = {
  name: string;
  age: number;
};

type Employee = {
  company: string;
  salary: number;
};

type EmployedPerson = Person & Employee;

const employee: EmployedPerson = {
  name: 'John',
  age: 30,
  company: 'ACME',
  salary: 50000
};

// Discriminated unions
type Circle = {
  kind: 'circle';
  radius: number;
};

type Rectangle = {
  kind: 'rectangle';
  width: number;
  height: number;
};

type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
  }
}

Type Aliases & Interfaces

// Type alias
type Point = {
  x: number;
  y: number;
};

type ID = string | number;

// Interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // Optional
  readonly createdAt: Date;  // Readonly
}

const user: User = {
  id: 1,
  name: 'John',
  email: 'john@example.com',
  createdAt: new Date()
};

// Interface extension
interface Admin extends User {
  role: 'admin' | 'superadmin';
  permissions: string[];
}

const admin: Admin = {
  id: 1,
  name: 'Jane',
  email: 'jane@example.com',
  createdAt: new Date(),
  role: 'admin',
  permissions: ['read', 'write']
};

// Interface merging (declaration merging)
interface Window {
  myCustomProperty: string;
}

interface Window {
  myOtherProperty: number;
}

// Now Window has both properties

// Type vs Interface:
// - Interface: can be extended, supports declaration merging
// - Type: more flexible, can use unions/intersections
// - Use interface for object shapes, type for everything else

Type Assertions & Type Guards

// Type assertion
const value: any = 'hello';
const length1 = (value as string).length;
const length2 = (<string>value).length;  // Alternative syntax

// Non-null assertion
function getValue(id: string | null): string {
  return id!.toUpperCase();  // Asserts id is not null
}

// Type guards
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase());  // TypeScript knows it's string
  } else {
    console.log(value.toFixed(2));  // TypeScript knows it's number
  }
}

// typeof guard
function example(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

// instanceof guard
class Dog {
  bark() { console.log('Woof!'); }
}

class Cat {
  meow() { console.log('Meow!'); }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// in operator
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}
TypeScript

Interview Questions

TypeScript Interview Questions Comprehensive TypeScript interview questions covering type system, generics, utility types, and best practices: Fundamentals 1. W

TypeScript Interview Questions

Comprehensive TypeScript interview questions covering type system, generics, utility types, and best practices:

Fundamentals

1. What is TypeScript?

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing, interfaces, enums, and other features for better tooling and error detection.

2. What are the benefits of TypeScript?

Static typing catches errors at compile time, better IDE support (autocomplete, refactoring), improved code documentation, easier refactoring, enhanced code quality and maintainability.

3. What is the difference between any and unknown?

any: disables type checking, allows any operation. unknown: type-safe version of any, requires type checking before use. Always prefer unknown over any.

4. What is the difference between interface and type?

interface: can be extended, supports declaration merging, better for object shapes. type: more flexible, supports unions/intersections/primitives, cannot be reopened. Use interface for public APIs, type for complex types.

5. What are generics?

Generics create reusable components that work with multiple types. They provide type variables that capture the type provided by the user.

Type System

6. What is type assertion?

Type assertion tells TypeScript to treat a value as a specific type. Syntax: value as Type or <Type>value. Use sparingly as it bypasses type checking.

7. What are type guards?

Type guards narrow the type within a conditional block. Built-in: typeof, instanceof, in operator. Custom: user-defined type predicate functions (value is Type).

8. What is type narrowing?

Type narrowing is TypeScript's process of refining types to more specific types through type guards, equality checks, and control flow analysis.

9. What is never type?

never represents values that never occur. Used for functions that always throw errors or have infinite loops. Also used in exhaustiveness checking.

10. What is void vs never?

void: function returns undefined or nothing. never: function never returns (throws or infinite loop).

Utility Types

11. What is Partial<T>?

Makes all properties of T optional. Useful for update operations where you might only update some fields.

12. What is Pick<T, K>?

Creates a type by picking specific properties K from T. Useful for creating smaller types from larger ones.

13. What is Omit<T, K>?

Creates a type by omitting specific properties K from T. Opposite of Pick.

14. What is Record<K, T>?

Creates an object type with keys of type K and values of type T. Useful for creating dictionaries.

15. What is ReturnType<T>?

Extracts the return type of a function type. Useful when you need to reference a function's return type without redefining it.

Advanced

16. What are conditional types?

Conditional types select one of two possible types based on a condition (T extends U ? X : Y). Enable advanced type transformations.

17. What is keyof?

keyof creates a union of an object type's keys. Useful for creating mapped types and accessing properties safely.

18. What is typeof?

typeof in TypeScript gets the type of a variable or property. Useful for extracting types from values.

19. What are template literal types?

Template literal types build new string literal types from existing ones. Useful for creating typed string patterns.

20. What is the difference between enum and const enum?

enum: generates JavaScript code. const enum: inlined at compile time, no JavaScript generated. const enum is more performant but less flexible.

TypeScript

Type Guards & Narrowing

TypeScript Type Guards & Narrowing Type Narrowing // typeof narrowing function padLeft(value: string, padding: string | number) { if (typeof padding === 'number

TypeScript Type Guards & Narrowing

Type Narrowing

// typeof narrowing
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + value;
  }
  return padding + value;
}

// Truthiness narrowing
function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === 'object') {
    // TypeScript knows strs is string[]
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === 'string') {
    console.log(strs);
  }
}

// Equality narrowing
function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // x and y must both be string
    x.toUpperCase();
    y.toUpperCase();
  }
}

// in operator narrowing
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}

// instanceof narrowing
function logValue(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toUTCString());
  } else {
    console.log(value.toUpperCase());
  }
}
TypeScript

Advanced Types (Generics & Utility Types)

TypeScript Advanced Types Generics // Generic function function identity<T>(arg: T): T { return arg; } const num = identity<number>(42); // Explicit const str =

TypeScript Advanced Types

Generics

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(42);  // Explicit
const str = identity('hello');     // Inferred

// Generic with constraints
interface Lengthwise {
  length: number;
}

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

logLength('hello');       // ✅ string has length
logLength([1, 2, 3]);     // ✅ array has length
// logLength(42);         // ❌ Error - number doesn't have length

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: 'John' },
  status: 200,
  message: 'Success'
};

const postsResponse: ApiResponse<Post[]> = {
  data: [{ id: 1, title: 'Post 1' }],
  status: 200,
  message: 'Success'
};

// Generic class
class DataStore<T> {
  private data: T[] = [];
  
  add(item: T): void {
    this.data.push(item);
  }
  
  get(index: number): T | undefined {
    return this.data[index];
  }
  
  getAll(): T[] {
    return this.data;
  }
  
  find(predicate: (item: T) => boolean): T | undefined {
    return this.data.find(predicate);
  }
}

const numberStore = new DataStore<number>();
numberStore.add(1);
numberStore.add(2);

const userStore = new DataStore<User>();
userStore.add({ id: 1, name: 'John' });

Utility Types

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

// Partial<T> - all properties optional
type PartialUser = Partial<User>;
const update: PartialUser = { name: 'Jane' };  // ✅ Only name

// Required<T> - all properties required
type RequiredUser = Required<User>;

// Readonly<T> - all properties readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: 'John', email: 'john@example.com', age: 30 };
// user.name = 'Jane';  // ❌ Error

// Pick<T, K> - pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
const preview: UserPreview = { id: 1, name: 'John' };

// Omit<T, K> - omit specific properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record<K, T> - object with keys K and values T
type Roles = 'admin' | 'user' | 'guest';
type Permissions = Record<Roles, string[]>;

const permissions: Permissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read']
};

// Exclude<T, U> - exclude types
type T1 = Exclude<'a' | 'b' | 'c', 'a'>;  // 'b' | 'c'

// Extract<T, U> - extract types
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>;  // 'a' | 'b'

// NonNullable<T> - remove null and undefined
type T3 = NonNullable<string | null | undefined>;  // string

// ReturnType<T> - function return type
function getUser() {
  return { id: 1, name: 'John' };
}

type User = ReturnType<typeof getUser>;  // { id: number; name: string }

// Parameters<T> - function parameter types as tuple
type Params = Parameters<typeof getUser>;  // []

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

type AddParams = Parameters<typeof add>;  // [number, number]

Mapped Types

// Basic mapped type
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// Mapped type with modification
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];  // Remove readonly
};

type Concrete<T> = {
  [P in keyof T]-?: T[P];  // Remove optional
};

// Transform property types
type Stringify<T> = {
  [P in keyof T]: string;
};

interface Numbers {
  a: number;
  b: number;
}

type Strings = Stringify<Numbers>;  // { a: string; b: string }
TypeScript

Enums & Literal Types

TypeScript Enums & Literal Types Enums Enums allow you to define a set of named constants, making it easier to document intent and create distinct cases. // Num

TypeScript Enums & Literal Types

Enums

Enums allow you to define a set of named constants, making it easier to document intent and create distinct cases.

// Numeric enum
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

let dir: Direction = Direction.Up;
console.log(dir); // 0
console.log(Direction[0]); // 'Up' (reverse mapping)

// Custom numeric values
enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}

function handleResponse(status: HttpStatus): void {
  switch (status) {
    case HttpStatus.OK:
      console.log('Success');
      break;
    case HttpStatus.NotFound:
      console.log('Resource not found');
      break;
    default:
      console.log('Other status');
  }
}

// String enum
enum LogLevel {
  Error = 'ERROR',
  Warning = 'WARNING',
  Info = 'INFO',
  Debug = 'DEBUG'
}

function log(level: LogLevel, message: string): void {
  console.log(`[${level}] ${message}`);
}

log(LogLevel.Error, 'Something went wrong');

// Const enum (inlined at compile time)
const enum Colors {
  Red = '#FF0000',
  Green = '#00FF00',
  Blue = '#0000FF'
}

const color = Colors.Red; // Inlined as '#FF0000'

// Heterogeneous enum (not recommended)
enum Mixed {
  No = 0,
  Yes = 'YES'
}

Literal Types

// String literals
type CardinalDirection = 'North' | 'South' | 'East' | 'West';
let direction: CardinalDirection = 'North';
// direction = 'Up'; // ❌ Error

// Number literals
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4;
// roll = 7; // ❌ Error

// Boolean literals
type True = true;
type False = false;

// Template literal types
type Greeting = `Hello ${string}`;
const hi: Greeting = 'Hello World';  // ✅
// const bad: Greeting = 'Hi World';  // ❌ Error

type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// Result: 'onClick' | 'onFocus' | 'onBlur'

type PropEventSource<T> = {
  on<K extends string & keyof T>(eventName: `${K}Changed`, callback: (newValue: T[K]) => void): void;
};

// Literal inference
const req = { method: 'GET' as const }; // Type: { method: 'GET' }
// vs
const req2 = { method: 'GET' }; // Type: { method: string }

When to Use Enum vs Literal Types

  • Use Enums when: You need reverse mapping, want a runtime object, or need to iterate over values

  • Use Literal Types when: You want compile-time only types, smaller bundle size, or better tree-shaking

TypeScript

Advanced Patterns

TypeScript Advanced Patterns Conditional Types // Basic conditional type type IsString<T> = T extends string ? true : false; type A = IsString<string>; // true

TypeScript Advanced Patterns

Conditional Types

// Basic conditional type
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Practical example
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string>;    // string
type Num = Flatten<number[]>;  // number

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;

type StrOrNum = string | number;
type Arrays = ToArray<StrOrNum>;  // string[] | number[]

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

function getUser() {
  return { id: 1, name: 'John' };
}

type UserType = GetReturnType<typeof getUser>;
// { id: number; name: string }

// Nested conditional types
type Unpromisify<T> = T extends Promise<infer U>
  ? U extends Promise<infer V>
    ? Unpromisify<V>
    : U
  : T;

type A = Unpromisify<Promise<string>>;  // string
type B = Unpromisify<Promise<Promise<number>>>;  // number

Mapped Types Advanced

// Make properties nullable
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

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

type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }

// Get function properties
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

class MyClass {
  name: string = '';
  count: number = 0;
  
  method1() {}
  method2() {}
}

type Methods = FunctionProperties<MyClass>;
// { method1: () => void; method2: () => void }

// Deep readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
  };
  cache: {
    ttl: number;
  };
}

type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly

Type Challenges

// Deep Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// Awaited (built-in TypeScript utility)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

type A = Awaited<Promise<string>>;  // string
type B = Awaited<Promise<Promise<number>>>;  // number

// Required keys
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

interface Example {
  a: string;
  b?: number;
  c: boolean;
}

type ReqKeys = RequiredKeys<Example>;  // 'a' | 'c'

// Readonly keys
type ReadonlyKeys<T> = {
  [K in keyof T]-?: (<F>() => F extends { [Q in K]: T[K] } ? 1 : 2) extends
    (<F>() => F extends { -readonly [Q in K]: T[K] } ? 1 : 2)
      ? never
      : K;
}[keyof T];
TypeScript

Decorators & Metadata

TypeScript Decorators & Metadata Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. They are functions

TypeScript Decorators & Metadata

Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. They are functions that modify classes and their members.

Class Decorators

// Simple class decorator
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Person {
  constructor(public name: string) {}
}

// Decorator factory (configurable)
function logger(prefix: string) {
  return function(constructor: Function) {
    console.log(`${prefix}: ${constructor.name}`);
  };
}

@logger('Creating')
class User {
  constructor(public name: string) {}
}

// Decorator that replaces constructor
function timestamp<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    timestamp = new Date();
  };
}

@timestamp
class Document {
  constructor(public title: string) {}
}

const doc = new Document('My Doc');
console.log((doc as any).timestamp); // Date object

Method Decorators

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    const result = original.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3); // Logs: Calling add with [2, 3], Result: 5

// Performance measurement
function measure(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  
  descriptor.value = async function(...args: any[]) {
    const start = performance.now();
    const result = await original.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} took ${(end - start).toFixed(2)}ms`);
    return result;
  };
  
  return descriptor;
}

class DataProcessor {
  @measure
  async processData(data: any[]): Promise<any[]> {
    // Simulate processing
    await new Promise(resolve => setTimeout(resolve, 100));
    return data.map(x => x * 2);
  }
}

Property & Parameter Decorators

// Property decorator
function required(target: any, propertyKey: string) {
  let value: any;
  
  const getter = () => value;
  const setter = (newValue: any) => {
    if (!newValue) {
      throw new Error(`${propertyKey} is required`);
    }
    value = newValue;
  };
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class User {
  @required
  email!: string;
  
  name?: string;
}

const user = new User();
// user.email = ''; // Error: email is required
user.email = 'test@example.com'; // ✅

// Parameter decorator
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Parameter decorator: ${propertyKey}[${parameterIndex}]`);
}

class Greeter {
  greet(@logParameter message: string): void {
    console.log(message);
  }
}
TypeScript

Configuration & Best Practices

TypeScript Configuration & Best Practices tsconfig.json Essentials { "compilerOptions": { // Language version "target": "ES2022", "lib": ["ES2022", "DOM"], // M

TypeScript Configuration & Best Practices

tsconfig.json Essentials

{
  "compilerOptions": {
    // Language version
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    
    // Module system
    "module": "ESNext",
    "moduleResolution": "bundler",
    
    // Strict type checking (recommended)
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    
    // Additional checks
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    
    // Emit
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    
    // Interop
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    
    // Advanced
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Best Practices

1. Always Enable Strict Mode

// ❌ BAD: Without strict mode
function greet(name) {  // any type
  return name.toUpperCase();
}

greet(null); // Runtime error!

// ✅ GOOD: With strict mode
function greet(name: string): string {
  return name.toUpperCase();
}

// greet(null); // ✅ Compile error!

2. Avoid using any

// ❌ BAD
function process(data: any): any {
  return data.value;
}

// ✅ GOOD: Use unknown for true unknowns
function process(data: unknown): string {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return String((data as any).value);
  }
  throw new Error('Invalid data');
}

// ✅ BETTER: Use generics
function process<T extends { value: string }>(data: T): string {
  return data.value;
}

3. Use Type Inference

// ❌ Redundant
const name: string = 'John';
const age: number = 30;

// ✅ GOOD: Let TypeScript infer
const name = 'John';  // Inferred as string
const age = 30;       // Inferred as number

// ✅ Do annotate function returns (for clarity)
function getUser(): User {
  return { id: 1, name: 'John' };
}

4. Prefer Interfaces for Objects

// ✅ GOOD: Interface for objects
interface User {
  id: number;
  name: string;
}

interface Admin extends User {
  role: string;
}

// Use type for unions, primitives, tuples
type ID = string | number;
type Point = [number, number];

5. Use Const Assertions

// Without const assertion
const colors = ['red', 'green', 'blue']; // Type: string[]

// ✅ With const assertion
const colors = ['red', 'green', 'blue'] as const;
// Type: readonly ['red', 'green', 'blue']

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;
// All properties become readonly

// Use for exact literal types
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
TypeScript

Decorators & Metadata

TypeScript Decorators & Metadata Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. They are functions

TypeScript Decorators & Metadata

Decorators provide a way to add annotations and meta-programming syntax for class declarations and members. They are functions that modify classes and their members.

Class Decorators

// Simple class decorator
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Person {
  constructor(public name: string) {}
}

// Decorator factory (configurable)
function logger(prefix: string) {
  return function(constructor: Function) {
    console.log(`${prefix}: ${constructor.name}`);
  };
}

@logger('Creating')
class User {
  constructor(public name: string) {}
}

// Decorator that replaces constructor
function timestamp<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    timestamp = new Date();
  };
}

@timestamp
class Document {
  constructor(public title: string) {}
}

const doc = new Document('My Doc');
console.log((doc as any).timestamp); // Date object

Method Decorators

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    const result = original.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
  
  return descriptor;
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
calc.add(2, 3); // Logs: Calling add with [2, 3], Result: 5

// Performance measurement
function measure(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  
  descriptor.value = async function(...args: any[]) {
    const start = performance.now();
    const result = await original.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} took ${(end - start).toFixed(2)}ms`);
    return result;
  };
  
  return descriptor;
}

class DataProcessor {
  @measure
  async processData(data: any[]): Promise<any[]> {
    // Simulate processing
    await new Promise(resolve => setTimeout(resolve, 100));
    return data.map(x => x * 2);
  }
}

Property & Parameter Decorators

// Property decorator
function required(target: any, propertyKey: string) {
  let value: any;
  
  const getter = () => value;
  const setter = (newValue: any) => {
    if (!newValue) {
      throw new Error(`${propertyKey} is required`);
    }
    value = newValue;
  };
  
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class User {
  @required
  email!: string;
  
  name?: string;
}

const user = new User();
// user.email = ''; // Error: email is required
user.email = 'test@example.com'; // ✅

// Parameter decorator
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`Parameter decorator: ${propertyKey}[${parameterIndex}]`);
}

class Greeter {
  greet(@logParameter message: string): void {
    console.log(message);
  }
}
TypeScript

Type Guards & Narrowing

TypeScript Type Guards & Narrowing Type Narrowing // typeof narrowing function padLeft(value: string, padding: string | number) { if (typeof padding === 'number

TypeScript Type Guards & Narrowing

Type Narrowing

// typeof narrowing
function padLeft(value: string, padding: string | number) {
  if (typeof padding === 'number') {
    return ' '.repeat(padding) + value;
  }
  return padding + value;
}

// Truthiness narrowing
function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === 'object') {
    // TypeScript knows strs is string[]
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === 'string') {
    console.log(strs);
  }
}

// Equality narrowing
function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // x and y must both be string
    x.toUpperCase();
    y.toUpperCase();
  }
}

// in operator narrowing
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}

// instanceof narrowing
function logValue(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toUTCString());
  } else {
    console.log(value.toUpperCase());
  }
}
TypeScript

Type System & Annotations

TypeScript Type System & Annotations TypeScript extends JavaScript by adding static type definitions. Types provide a way to describe the shape of objects, enab

TypeScript Type System & Annotations

TypeScript extends JavaScript by adding static type definitions. Types provide a way to describe the shape of objects, enabling better documentation and error detection.

Basic Types

// Primitives
let name: string = 'John';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// BigInt and Symbol
let bigNumber: bigint = 100n;
let uniqueId: symbol = Symbol('id');

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// Tuples - fixed length and types
let tuple: [string, number] = ['age', 30];
let RGB: [number, number, number] = [255, 0, 0];

// Tuple with optional and rest
let optional: [string, number?] = ['hello'];
let rest: [string, ...number[]] = ['numbers', 1, 2, 3];

// Objects
let user: { name: string; age: number } = {
  name: 'John',
  age: 30
};

// Optional properties
let person: { name: string; age?: number } = { name: 'Jane' };

// Readonly properties
let readonlyUser: { readonly id: number; name: string } = {
  id: 1,
  name: 'John'
};
// readonlyUser.id = 2;  // Error!

// Index signatures
interface StringMap {
  [key: string]: string;
}

const map: StringMap = {
  name: 'John',
  email: 'john@example.com'
};

Function Types

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

// Arrow functions
const multiply = (a: number, b: number): number => a * b;

// Optional parameters
function greet(name: string, greeting?: string): string {
  return `${greeting || 'Hello'}, ${name}`;
}

// Default parameters
function createUser(name: string, age: number = 18): User {
  return { name, age };
}

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

// Function type
type MathOperation = (a: number, b: number) => number;

const divide: MathOperation = (a, b) => a / b;

// Void return type
function log(message: string): void {
  console.log(message);
}

// Never return type (never returns)
function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

// Function overloads
function getValue(value: string): string;
function getValue(value: number): number;
function getValue(value: boolean): boolean;
function getValue(value: string | number | boolean): string | number | boolean {
  return value;
}

const str = getValue('hello');  // Type: string
const num = getValue(42);       // Type: number

Union & Intersection Types

// Union types (OR)
let id: string | number;
id = 'abc';  // ✅
id = 123;    // ✅
// id = true;   // ❌ Error

type Status = 'pending' | 'active' | 'completed';
let status: Status = 'pending';  // ✅
// status = 'invalid';  // ❌ Error

// Intersection types (AND)
type Person = {
  name: string;
  age: number;
};

type Employee = {
  company: string;
  salary: number;
};

type EmployedPerson = Person & Employee;

const employee: EmployedPerson = {
  name: 'John',
  age: 30,
  company: 'ACME',
  salary: 50000
};

// Discriminated unions
type Circle = {
  kind: 'circle';
  radius: number;
};

type Rectangle = {
  kind: 'rectangle';
  width: number;
  height: number;
};

type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
  }
}

Type Aliases & Interfaces

// Type alias
type Point = {
  x: number;
  y: number;
};

type ID = string | number;

// Interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;  // Optional
  readonly createdAt: Date;  // Readonly
}

const user: User = {
  id: 1,
  name: 'John',
  email: 'john@example.com',
  createdAt: new Date()
};

// Interface extension
interface Admin extends User {
  role: 'admin' | 'superadmin';
  permissions: string[];
}

const admin: Admin = {
  id: 1,
  name: 'Jane',
  email: 'jane@example.com',
  createdAt: new Date(),
  role: 'admin',
  permissions: ['read', 'write']
};

// Interface merging (declaration merging)
interface Window {
  myCustomProperty: string;
}

interface Window {
  myOtherProperty: number;
}

// Now Window has both properties

// Type vs Interface:
// - Interface: can be extended, supports declaration merging
// - Type: more flexible, can use unions/intersections
// - Use interface for object shapes, type for everything else

Type Assertions & Type Guards

// Type assertion
const value: any = 'hello';
const length1 = (value as string).length;
const length2 = (<string>value).length;  // Alternative syntax

// Non-null assertion
function getValue(id: string | null): string {
  return id!.toUpperCase();  // Asserts id is not null
}

// Type guards
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase());  // TypeScript knows it's string
  } else {
    console.log(value.toFixed(2));  // TypeScript knows it's number
  }
}

// typeof guard
function example(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

// instanceof guard
class Dog {
  bark() { console.log('Woof!'); }
}

class Cat {
  meow() { console.log('Meow!'); }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}

// in operator
type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
  if ('swim' in animal) {
    animal.swim();
  } else {
    animal.fly();
  }
}
TypeScript

Advanced Types (Generics & Utility Types)

TypeScript Advanced Types Generics // Generic function function identity<T>(arg: T): T { return arg; } const num = identity<number>(42); // Explicit const str =

TypeScript Advanced Types

Generics

// Generic function
function identity<T>(arg: T): T {
  return arg;
}

const num = identity<number>(42);  // Explicit
const str = identity('hello');     // Inferred

// Generic with constraints
interface Lengthwise {
  length: number;
}

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

logLength('hello');       // ✅ string has length
logLength([1, 2, 3]);     // ✅ array has length
// logLength(42);         // ❌ Error - number doesn't have length

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: 1, name: 'John' },
  status: 200,
  message: 'Success'
};

const postsResponse: ApiResponse<Post[]> = {
  data: [{ id: 1, title: 'Post 1' }],
  status: 200,
  message: 'Success'
};

// Generic class
class DataStore<T> {
  private data: T[] = [];
  
  add(item: T): void {
    this.data.push(item);
  }
  
  get(index: number): T | undefined {
    return this.data[index];
  }
  
  getAll(): T[] {
    return this.data;
  }
  
  find(predicate: (item: T) => boolean): T | undefined {
    return this.data.find(predicate);
  }
}

const numberStore = new DataStore<number>();
numberStore.add(1);
numberStore.add(2);

const userStore = new DataStore<User>();
userStore.add({ id: 1, name: 'John' });

Utility Types

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

// Partial<T> - all properties optional
type PartialUser = Partial<User>;
const update: PartialUser = { name: 'Jane' };  // ✅ Only name

// Required<T> - all properties required
type RequiredUser = Required<User>;

// Readonly<T> - all properties readonly
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: 'John', email: 'john@example.com', age: 30 };
// user.name = 'Jane';  // ❌ Error

// Pick<T, K> - pick specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
const preview: UserPreview = { id: 1, name: 'John' };

// Omit<T, K> - omit specific properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record<K, T> - object with keys K and values T
type Roles = 'admin' | 'user' | 'guest';
type Permissions = Record<Roles, string[]>;

const permissions: Permissions = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read']
};

// Exclude<T, U> - exclude types
type T1 = Exclude<'a' | 'b' | 'c', 'a'>;  // 'b' | 'c'

// Extract<T, U> - extract types
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>;  // 'a' | 'b'

// NonNullable<T> - remove null and undefined
type T3 = NonNullable<string | null | undefined>;  // string

// ReturnType<T> - function return type
function getUser() {
  return { id: 1, name: 'John' };
}

type User = ReturnType<typeof getUser>;  // { id: number; name: string }

// Parameters<T> - function parameter types as tuple
type Params = Parameters<typeof getUser>;  // []

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

type AddParams = Parameters<typeof add>;  // [number, number]

Mapped Types

// Basic mapped type
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// Mapped type with modification
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];  // Remove readonly
};

type Concrete<T> = {
  [P in keyof T]-?: T[P];  // Remove optional
};

// Transform property types
type Stringify<T> = {
  [P in keyof T]: string;
};

interface Numbers {
  a: number;
  b: number;
}

type Strings = Stringify<Numbers>;  // { a: string; b: string }
TypeScript

Interview Questions

TypeScript Interview Questions Comprehensive TypeScript interview questions covering type system, generics, utility types, and best practices: Fundamentals 1. W

TypeScript Interview Questions

Comprehensive TypeScript interview questions covering type system, generics, utility types, and best practices:

Fundamentals

1. What is TypeScript?

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing, interfaces, enums, and other features for better tooling and error detection.

2. What are the benefits of TypeScript?

Static typing catches errors at compile time, better IDE support (autocomplete, refactoring), improved code documentation, easier refactoring, enhanced code quality and maintainability.

3. What is the difference between any and unknown?

any: disables type checking, allows any operation. unknown: type-safe version of any, requires type checking before use. Always prefer unknown over any.

4. What is the difference between interface and type?

interface: can be extended, supports declaration merging, better for object shapes. type: more flexible, supports unions/intersections/primitives, cannot be reopened. Use interface for public APIs, type for complex types.

5. What are generics?

Generics create reusable components that work with multiple types. They provide type variables that capture the type provided by the user.

Type System

6. What is type assertion?

Type assertion tells TypeScript to treat a value as a specific type. Syntax: value as Type or <Type>value. Use sparingly as it bypasses type checking.

7. What are type guards?

Type guards narrow the type within a conditional block. Built-in: typeof, instanceof, in operator. Custom: user-defined type predicate functions (value is Type).

8. What is type narrowing?

Type narrowing is TypeScript's process of refining types to more specific types through type guards, equality checks, and control flow analysis.

9. What is never type?

never represents values that never occur. Used for functions that always throw errors or have infinite loops. Also used in exhaustiveness checking.

10. What is void vs never?

void: function returns undefined or nothing. never: function never returns (throws or infinite loop).

Utility Types

11. What is Partial<T>?

Makes all properties of T optional. Useful for update operations where you might only update some fields.

12. What is Pick<T, K>?

Creates a type by picking specific properties K from T. Useful for creating smaller types from larger ones.

13. What is Omit<T, K>?

Creates a type by omitting specific properties K from T. Opposite of Pick.

14. What is Record<K, T>?

Creates an object type with keys of type K and values of type T. Useful for creating dictionaries.

15. What is ReturnType<T>?

Extracts the return type of a function type. Useful when you need to reference a function's return type without redefining it.

Advanced

16. What are conditional types?

Conditional types select one of two possible types based on a condition (T extends U ? X : Y). Enable advanced type transformations.

17. What is keyof?

keyof creates a union of an object type's keys. Useful for creating mapped types and accessing properties safely.

18. What is typeof?

typeof in TypeScript gets the type of a variable or property. Useful for extracting types from values.

19. What are template literal types?

Template literal types build new string literal types from existing ones. Useful for creating typed string patterns.

20. What is the difference between enum and const enum?

enum: generates JavaScript code. const enum: inlined at compile time, no JavaScript generated. const enum is more performant but less flexible.

TypeScript

Configuration & Best Practices

TypeScript Configuration & Best Practices tsconfig.json Essentials { "compilerOptions": { // Language version "target": "ES2022", "lib": ["ES2022", "DOM"], // M

TypeScript Configuration & Best Practices

tsconfig.json Essentials

{
  "compilerOptions": {
    // Language version
    "target": "ES2022",
    "lib": ["ES2022", "DOM"],
    
    // Module system
    "module": "ESNext",
    "moduleResolution": "bundler",
    
    // Strict type checking (recommended)
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    
    // Additional checks
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    
    // Emit
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    
    // Interop
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    
    // Advanced
    "skipLibCheck": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Best Practices

1. Always Enable Strict Mode

// ❌ BAD: Without strict mode
function greet(name) {  // any type
  return name.toUpperCase();
}

greet(null); // Runtime error!

// ✅ GOOD: With strict mode
function greet(name: string): string {
  return name.toUpperCase();
}

// greet(null); // ✅ Compile error!

2. Avoid using any

// ❌ BAD
function process(data: any): any {
  return data.value;
}

// ✅ GOOD: Use unknown for true unknowns
function process(data: unknown): string {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return String((data as any).value);
  }
  throw new Error('Invalid data');
}

// ✅ BETTER: Use generics
function process<T extends { value: string }>(data: T): string {
  return data.value;
}

3. Use Type Inference

// ❌ Redundant
const name: string = 'John';
const age: number = 30;

// ✅ GOOD: Let TypeScript infer
const name = 'John';  // Inferred as string
const age = 30;       // Inferred as number

// ✅ Do annotate function returns (for clarity)
function getUser(): User {
  return { id: 1, name: 'John' };
}

4. Prefer Interfaces for Objects

// ✅ GOOD: Interface for objects
interface User {
  id: number;
  name: string;
}

interface Admin extends User {
  role: string;
}

// Use type for unions, primitives, tuples
type ID = string | number;
type Point = [number, number];

5. Use Const Assertions

// Without const assertion
const colors = ['red', 'green', 'blue']; // Type: string[]

// ✅ With const assertion
const colors = ['red', 'green', 'blue'] as const;
// Type: readonly ['red', 'green', 'blue']

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;
// All properties become readonly

// Use for exact literal types
type Color = typeof colors[number]; // 'red' | 'green' | 'blue'
TypeScript

Advanced Patterns

TypeScript Advanced Patterns Conditional Types // Basic conditional type type IsString<T> = T extends string ? true : false; type A = IsString<string>; // true

TypeScript Advanced Patterns

Conditional Types

// Basic conditional type
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Practical example
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string>;    // string
type Num = Flatten<number[]>;  // number

// Distributive conditional types
type ToArray<T> = T extends any ? T[] : never;

type StrOrNum = string | number;
type Arrays = ToArray<StrOrNum>;  // string[] | number[]

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

function getUser() {
  return { id: 1, name: 'John' };
}

type UserType = GetReturnType<typeof getUser>;
// { id: number; name: string }

// Nested conditional types
type Unpromisify<T> = T extends Promise<infer U>
  ? U extends Promise<infer V>
    ? Unpromisify<V>
    : U
  : T;

type A = Unpromisify<Promise<string>>;  // string
type B = Unpromisify<Promise<Promise<number>>>;  // number

Mapped Types Advanced

// Make properties nullable
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

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

type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }

// Get function properties
type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

class MyClass {
  name: string = '';
  count: number = 0;
  
  method1() {}
  method2() {}
}

type Methods = FunctionProperties<MyClass>;
// { method1: () => void; method2: () => void }

// Deep readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};

interface Config {
  database: {
    host: string;
    port: number;
  };
  cache: {
    ttl: number;
  };
}

type ReadonlyConfig = DeepReadonly<Config>;
// All nested properties are readonly

Type Challenges

// Deep Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// Awaited (built-in TypeScript utility)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

type A = Awaited<Promise<string>>;  // string
type B = Awaited<Promise<Promise<number>>>;  // number

// Required keys
type RequiredKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];

interface Example {
  a: string;
  b?: number;
  c: boolean;
}

type ReqKeys = RequiredKeys<Example>;  // 'a' | 'c'

// Readonly keys
type ReadonlyKeys<T> = {
  [K in keyof T]-?: (<F>() => F extends { [Q in K]: T[K] } ? 1 : 2) extends
    (<F>() => F extends { -readonly [Q in K]: T[K] } ? 1 : 2)
      ? never
      : K;
}[keyof T];
TypeScript

Enums & Literal Types

TypeScript Enums & Literal Types Enums Enums allow you to define a set of named constants, making it easier to document intent and create distinct cases. // Num

TypeScript Enums & Literal Types

Enums

Enums allow you to define a set of named constants, making it easier to document intent and create distinct cases.

// Numeric enum
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

let dir: Direction = Direction.Up;
console.log(dir); // 0
console.log(Direction[0]); // 'Up' (reverse mapping)

// Custom numeric values
enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  ServerError = 500
}

function handleResponse(status: HttpStatus): void {
  switch (status) {
    case HttpStatus.OK:
      console.log('Success');
      break;
    case HttpStatus.NotFound:
      console.log('Resource not found');
      break;
    default:
      console.log('Other status');
  }
}

// String enum
enum LogLevel {
  Error = 'ERROR',
  Warning = 'WARNING',
  Info = 'INFO',
  Debug = 'DEBUG'
}

function log(level: LogLevel, message: string): void {
  console.log(`[${level}] ${message}`);
}

log(LogLevel.Error, 'Something went wrong');

// Const enum (inlined at compile time)
const enum Colors {
  Red = '#FF0000',
  Green = '#00FF00',
  Blue = '#0000FF'
}

const color = Colors.Red; // Inlined as '#FF0000'

// Heterogeneous enum (not recommended)
enum Mixed {
  No = 0,
  Yes = 'YES'
}

Literal Types

// String literals
type CardinalDirection = 'North' | 'South' | 'East' | 'West';
let direction: CardinalDirection = 'North';
// direction = 'Up'; // ❌ Error

// Number literals
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let roll: DiceRoll = 4;
// roll = 7; // ❌ Error

// Boolean literals
type True = true;
type False = false;

// Template literal types
type Greeting = `Hello ${string}`;
const hi: Greeting = 'Hello World';  // ✅
// const bad: Greeting = 'Hi World';  // ❌ Error

type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// Result: 'onClick' | 'onFocus' | 'onBlur'

type PropEventSource<T> = {
  on<K extends string & keyof T>(eventName: `${K}Changed`, callback: (newValue: T[K]) => void): void;
};

// Literal inference
const req = { method: 'GET' as const }; // Type: { method: 'GET' }
// vs
const req2 = { method: 'GET' }; // Type: { method: string }

When to Use Enum vs Literal Types

  • Use Enums when: You need reverse mapping, want a runtime object, or need to iterate over values

  • Use Literal Types when: You want compile-time only types, smaller bundle size, or better tree-shaking

Keep your TypeScript knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever