Core Types
// Primitives
let name: string = 'Ajay';
let age: number = 10;
let active: boolean = true;
// Arrays
let trades: string[] = [];
let prices: Array<number> = [];
// Tuple — fixed length, fixed types
let entry: [string, number] = ['XAUUSD', 1920.50];
// Union — one of these types
let id: string | number = 'trade-123';
// Intersection — all of these types combined
type AdminUser = User & { adminLevel: number };
// any — opt out of type checking (avoid)
// unknown — safer alternative to any, must narrow before use
// never — function that never returns (throws or infinite loops)
// void — function that returns nothing
Interfaces vs Types
// Interface — for object shapes, extendable
interface Trade {
id: string;
instrument: string;
quantity: number;
}
interface MetalTrade extends Trade {
metal: 'gold' | 'silver' | 'platinum';
}
// Type alias — for unions, primitives, computed shapes
type Status = 'pending' | 'filled' | 'cancelled';
type TradeOrNull = Trade | null;Interview answer: Prefer interface for object shapes — it's extendable and shows intent. Use type for unions, intersections, and aliases. In practice they're interchangeable for objects, but teams should pick one and be consistent.
Generics
Reusable logic that works across types without losing type safety.
// Generic function
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
first([1, 2, 3]); // returns number
first(['a', 'b']); // returns string
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
error: string | null;
}
const response: ApiResponse<Trade[]> = {
data: [],
status: 200,
error: null
};
// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Utility Types
These come up constantly in interviews. Know all of them.
interface Trade {
id: string;
instrument: string;
quantity: number;
status: string;
}
// Partial — all fields optional
type DraftTrade = Partial<Trade>;
// Required — all fields required
type CompleteTrade = Required<Trade>;
// Pick — keep only these fields
type TradePreview = Pick<Trade, 'id' | 'instrument'>;
// Omit — remove these fields
type TradeWithoutId = Omit<Trade, 'id'>;
// Record — key/value map with typed keys and values
type StatusMap = Record<string, Trade[]>;
// Readonly — no mutation
const trade: Readonly<Trade> = { id: '1', instrument: 'XAUUSD', quantity: 10, status: 'pending' };
// ReturnType — infer return type of a function
function fetchTrade() { return { id: '1', instrument: 'XAUUSD' }; }
type TradeResult = ReturnType<typeof fetchTrade>;
// Parameters — infer param types of a function
type FetchParams = Parameters<typeof fetchTrade>;
Discriminated Unions
Most powerful TypeScript pattern for complex state. Very relevant for trading UIs.
type OrderState =
| { status: 'pending' }
| { status: 'filled'; fillPrice: number; fillTime: Date }
| { status: 'cancelled'; reason: string }
| { status: 'rejected'; code: number; message: string };
function handleOrder(order: OrderState) {
switch (order.status) {
case 'filled':
console.log(order.fillPrice); // TS knows fillPrice exists here
break;
case 'rejected':
console.log(order.message); // TS knows message exists here
break;
}
}
Why it matters: TypeScript narrows the type inside each case. You get autocomplete and safety without casting. Use this for anything with multiple states: API responses, form states, WebSocket message types.
Type Narrowing
// typeof
function process(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase(); // TS knows it's string here
}
return value.toFixed(2); // TS knows it's number here
}
// instanceof
function logError(error: Error | string) {
if (error instanceof Error) {
console.log(error.message);
}
}
// Type guard — custom narrowing function
function isTrade(obj: unknown): obj is Trade {
return typeof obj === 'object' && obj !== null && 'instrument' in obj;
}
// in operator
function handleMessage(msg: { type: 'order' } | { type: 'price'; value: number }) {
if ('value' in msg) {
console.log(msg.value); // TS knows it's the price message
}
}
keyof and typeof
// keyof — get union of all keys
type TradeKeys = keyof Trade; // 'id' | 'instrument' | 'quantity' | 'status'
// typeof — get type from a value
const defaultTrade = { id: '', instrument: '', quantity: 0, status: 'pending' };
type TradeShape = typeof defaultTrade;
// Together — safe property access
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Enums
// Prefer const enum — erased at compile time, no runtime object
const enum Direction { Up, Down, Left, Right }
// String enum — readable in logs and network payloads
enum TradeStatus {
Pending = 'PENDING',
Filled = 'FILLED',
Cancelled = 'CANCELLED'
}
// Interview note: many teams now prefer union types over enums
type TradeStatus = 'PENDING' | 'FILLED' | 'CANCELLED';
// Reason: simpler, no import needed, works with discriminated unions
Type Assertions and Non-Null Assertion
// Type assertion — tell TS you know better
const input = document.getElementById('price') as HTMLInputElement;
// Non-null assertion — tell TS the value is not null/undefined
const value = input.value!;
// Both are escape hatches, use sparingly.
// Prefer type guards over assertions.
Declaration Merging and Module Augmentation
// Extend third-party types
declare module 'express' {
interface Request {
user?: { id: string; role: string };
}
}
// Useful for adding custom properties to libraries
// Common in enterprise apps that extend platform types (AG Grid, etc.)
Mapped Types
// Make all properties of T optional and nullable
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Make all properties readonly
type Immutable<T> = { readonly [K in keyof T]: T[K] };
// Transform values
type Stringified<T> = { [K in keyof T]: string };
Conditional Types
// T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
// Practical use — extract return type conditionally
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type A = Unwrap<Promise<string>>; // string
type B = Unwrap<number>; // number