Skip to content

1. Types & Interfaces ​


1. Basic Types ​

ts
let name: string = "Dipak";
let age: number = 25;
let isActive: boolean = true;
let nothing: null = null;
let notSet: undefined = undefined;
let big: bigint = 123n;
let sym: symbol = Symbol("id");

Type Inference — Prefer It ​

ts
let city = "Kolkata";   // inferred as string
// let city: string = "Kolkata";   // redundant

Annotate function parameters and return types and exported values. Let TypeScript infer local variables.

Arrays and Tuples ​

ts
let nums: number[] = [1, 2, 3];
let names: Array<string> = ["a", "b"];

// Tuple — fixed length, fixed types per position
let point: [number, number] = [10, 20];
let entry: [string, number, boolean?] = ["a", 1];

// Named tuple — better error messages
let coord: [x: number, y: number] = [1, 2];

Tuples are what useState returns: [T, Dispatch<SetStateAction<T>>].


2. any vs unknown vs never ​

A guaranteed interview question.

any — Turns Off TypeScript ​

ts
let value: any = "hello";
value.foo.bar.baz();   // compiles, crashes at runtime

any is contagious. It spreads through every expression it touches.

unknown — The Safe any ​

ts
let value: unknown = "hello";

// value.toUpperCase();   // Error — must narrow first

if (typeof value === "string") {
  value.toUpperCase();   // OK
}

Anything is assignable to unknown, but unknown is assignable to nothing without narrowing.

never — Impossible ​

ts
function fail(msg: string): never {
  throw new Error(msg);   // never returns
}

type Impossible = string & number;   // never

Its practical use is exhaustiveness checking:

ts
type Status = "idle" | "loading" | "done";

function label(s: Status) {
  switch (s) {
    case "idle": return "Idle";
    case "loading": return "Loading";
    case "done": return "Done";
    default: {
      const exhaustive: never = s;   // compile error if a case is missing
      return exhaustive;
    }
  }
}

Add "error" to Status and this file fails to compile until you handle it. That is the pattern interviewers want to see.

Comparison ​

anyunknownnever
Assignable from anythingYesYesNo
Assignable to anythingYesNoYes
Requires narrowingNoYes—
SafeNoYesYes

Interview Point ​

Use unknown at every boundary where data enters your app — JSON.parse, API responses, catch blocks. Then narrow it. any at a boundary means the rest of your types are a fiction.


3. type vs interface ​

The most-asked TypeScript question.

Both Can Describe an Object ​

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

type UserType = {
  id: number;
  name: string;
};

Only interface Can Be Reopened (Declaration Merging) ​

ts
interface Window {
  myApp: { version: string };
}
// merges with the built-in Window

Only type Can Do Unions, Tuples and Primitives ​

ts
type Status = "idle" | "loading" | "error";
type Point = [number, number];
type ID = string | number;
type Handler = (e: Event) => void;

Extension Syntax ​

ts
interface Admin extends User {
  role: string;
}

type AdminType = UserType & { role: string };

Comparison ​

Featureinterfacetype
Object shapesYesYes
UnionsNoYes
Tuples / primitivesNoYes
Extendsextends&
Declaration mergingYesNo
Computed / mapped typesNoYes
Implements in a classYesYes (object types)

The Answer That Works ​

"Interface for object shapes that might be extended, especially public API surfaces and anything a library consumer might augment. Type for unions, tuples, function signatures and anything computed. In practice most teams pick one and stay consistent — the difference rarely matters."


4. Union and Intersection Types ​

Union — | — "one of these" ​

ts
type Result = string | number;

function format(id: string | number) {
  if (typeof id === "string") return id.toUpperCase();
  return id.toFixed(2);
}

Intersection — & — "all of these at once" ​

ts
type Timestamps = { createdAt: Date; updatedAt: Date };
type Post = { title: string } & Timestamps;
// { title, createdAt, updatedAt }

Interview Trap ​

Union is not "either type's properties". You can only access members present on every member of the union until you narrow.

ts
type A = { a: string; shared: string };
type B = { b: string; shared: string };

function f(x: A | B) {
  x.shared;   // OK — on both
  // x.a;     // Error — might be B
}

5. Literal Types ​

ts
type Direction = "up" | "down" | "left" | "right";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;

let dir: Direction = "up";
// dir = "diagonal";   // Error

Massively better than string for props, actions and status fields — you get autocomplete and typo protection.

as const ​

ts
const config = { mode: "dark", retries: 3 };
// inferred: { mode: string; retries: number }

const config = { mode: "dark", retries: 3 } as const;
// inferred: { readonly mode: "dark"; readonly retries: 3 }

Common pattern — derive a union from an array:

ts
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = typeof ROLES[number];   // "admin" | "editor" | "viewer"

One source of truth for both the runtime array and the type.


6. Optional, Readonly and Index Signatures ​

ts
interface User {
  id: number;
  name: string;
  email?: string;              // optional — string | undefined
  readonly createdAt: Date;    // cannot be reassigned
  [key: string]: unknown;      // index signature
}

readonly Is Shallow ​

ts
interface Config {
  readonly options: { debug: boolean };
}

config.options = {};            // Error
config.options.debug = true;    // Allowed — only the top level is readonly

Use Readonly<T> for one level, or a DeepReadonly helper for more.

? vs | undefined ​

ts
interface A { x?: number }              // may be absent entirely
interface B { x: number | undefined }   // must be present, may be undefined

const a: A = {};              // OK
const b: B = {};              // Error — x is required
const b2: B = { x: undefined };  // OK

Under exactOptionalPropertyTypes, ? and | undefined become properly distinct.


7. Functions ​

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

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

// Optional and default parameters
function greet(name: string, greeting = "Hello", title?: string): string {
  return `${greeting}, ${title ?? ""} ${name}`;
}

// Rest parameters
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

Function Type ​

ts
type Callback = (error: Error | null, data?: string) => void;
type Predicate<T> = (item: T) => boolean;

Overloads ​

ts
function parse(input: string): object;
function parse(input: string, raw: true): string;
function parse(input: string, raw?: boolean): object | string {
  return raw ? input : JSON.parse(input);
}

The implementation signature is not callable from outside — only the overloads are.

void vs undefined ​

ts
type Fn = () => void;

const f: Fn = () => 42;   // Allowed — the return value is just ignored

This is why array.forEach(x => arr.push(x)) compiles even though push returns a number. A void return type means "the caller must not rely on the return value", not "must return nothing".


8. Enums vs Union Literals ​

ts
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
}

Why Most Codebases Avoid Enums ​

  • Numeric enums are not type-safe — Status.Active = 5 compiles even if 5 isn't a member
  • Enums emit real JavaScript, so they are not fully erasable
  • const enum is banned under isolatedModules, which most bundlers require

The Modern Alternative ​

ts
const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE",
} as const;

type Status = typeof Status[keyof typeof Status];   // "ACTIVE" | "INACTIVE"

Zero runtime cost beyond a plain object, and fully type-safe.

Interview Point ​

Knowing why teams avoid enums scores better than knowing the syntax. TypeScript 5.0 also made all enums union enums, which fixed some but not all of the problems.


9. Type Assertions ​

ts
const input = document.getElementById("email") as HTMLInputElement;
input.value = "test";

Assertions Are Not Conversions ​

ts
const x = "hello" as unknown as number;   // lies to the compiler
x.toFixed(2);                              // crashes at runtime

An assertion says "trust me". If you are wrong, TypeScript cannot help you.

Non-Null Assertion — ! ​

ts
const el = document.querySelector("#app")!;   // "it's definitely not null"

Convenient, and the source of a lot of production null errors. Prefer an explicit check:

ts
const el = document.querySelector("#app");
if (!el) throw new Error("#app not found");

satisfies (TypeScript 4.9+) ​

The best of both worlds — validate against a type without widening the inferred one.

ts
const config = {
  mode: "dark",
  retries: 3,
} satisfies Config;

config.mode;   // still the literal "dark", not string

With : Config you would lose the literal. With satisfies you keep it and get the check. Worth mentioning — it signals you follow modern TypeScript.


10. strict Mode ​

json
{
  "compilerOptions": {
    "strict": true
  }
}

Turns on:

FlagEffect
strictNullChecksnull and undefined are not assignable to other types
noImplicitAnyError on a parameter TypeScript can't infer
strictFunctionTypesContravariant parameter checking
strictBindCallApplyType-checks bind, call, apply
strictPropertyInitializationClass fields must be initialised
noImplicitThisError on an untyped this
useUnknownInCatchVariablescatch (e) is unknown, not any

Interview Point ​

strictNullChecks is the flag that provides most of TypeScript's real-world value — it is what eliminates "cannot read property of undefined". Running without strict gives you autocomplete and very little safety. Always turn it on, from day one on a new project.

Other flags worth adding: noUncheckedIndexedAccess (makes arr[0] return T | undefined, which is the truth) and noUnusedLocals.

© 2025 DDocs · Dipak's Documentation Guide