Top 40 TypeScript Interview Questions 2026 — Complete Guide with Solutions
TypeScript isn't optional anymore — it's the price of admission. With 60%+ adoption among JavaScript developers, companies like Microsoft, Atlassian, Flipkart, Zepto, Groww, and CRED won't even consider you for mid-level roles without strong TypeScript skills. The payoff? TypeScript-proficient React/Node.js developers earn ₹18-40 LPA at product startups; senior TS architects pull ₹40-65 LPA at top companies.
TypeScript 5.x brought game-changing features — const type parameters, variadic tuple types, satisfies operator, and new decorator standards — and interviewers are already testing them. This guide covers 40 battle-tested questions compiled from real interviews at Microsoft, Atlassian, Flipkart, CRED, and Groww — from basics to the advanced type-level patterns that only senior engineers survive.
Related: React Interview Questions 2026 | Golang Interview Questions 2026 | Microservices Interview Questions 2026
Interview Format at Top Companies
| Company Tier | TypeScript Depth | Topics |
|---|---|---|
| FAANG/Unicorn | Deep | Advanced generics, conditional types, inference |
| Product Startup | Moderate | Practical types, utility types, patterns |
| Service/IT | Basic | Interfaces, enums, basic generics |
BEGINNER LEVEL — The Foundation (Questions 1–12)
Q1. What is TypeScript and why should you use it over JavaScript?
TypeScript is a statically-typed superset of JavaScript that compiles to plain JavaScript. Key advantages:
| Feature | JavaScript | TypeScript |
|---|---|---|
| Type checking | Runtime | Compile-time |
| IDE support | Basic | Full IntelliSense |
| Refactoring | Error-prone | Safe |
| Documentation | Manual | Types as docs |
| Null safety | None | Strict null checks |
TypeScript catches entire categories of bugs before they reach production: undefined property access, wrong argument types, exhaustiveness errors in switch statements.
Q2. What is the difference between interface and type?
Both define the shape of objects, but they differ in capabilities:
// Interface — extendable via declaration merging
interface User {
id: number;
name: string;
}
interface User {
email: string; // merged with above — works!
}
// Type alias — no declaration merging, but more powerful
type User = {
id: number;
name: string;
};
// type can represent things interface cannot
type StringOrNumber = string | number;
type Callback = (event: MouseEvent) => void;
type Tuple = [string, number, boolean];
Rule of thumb: Use interface for object shapes (especially public APIs/libraries). Use type for unions, intersections, and complex type aliases.
Q3. What are TypeScript utility types? List and explain the most important ones.
TypeScript ships with built-in generic utility types that transform existing types.
interface User {
id: number;
name: string;
email: string;
password: string;
}
// Partial — all properties optional
type UpdateUserDTO = Partial<User>;
// { id?: number; name?: string; email?: string; password?: string; }
// Required — all properties required
type RequiredUser = Required<Partial<User>>;
// Pick — select specific properties
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// Omit — exclude specific properties
type SafeUser = Omit<User, 'password'>;
// Readonly — prevent mutation
type ImmutableUser = Readonly<User>;
// Record — map keys to values
type RolePermissions = Record<'admin' | 'editor' | 'viewer', string[]>;
// ReturnType — extract function return type
async function fetchUser(): Promise<User> { /* ... */ }
type FetchResult = ReturnType<typeof fetchUser>; // Promise<User>
type AwaitedUser = Awaited<FetchResult>; // User
// Parameters — extract function parameter types
function createUser(name: string, email: string, role: string) {}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, email: string, role: string]
// Extract / Exclude
type Role = 'admin' | 'editor' | 'viewer' | 'guest';
type PrivilegedRole = Extract<Role, 'admin' | 'editor'>; // 'admin' | 'editor'
type PublicRole = Exclude<Role, 'admin' | 'editor'>; // 'viewer' | 'guest'
// NonNullable
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
Q4. What is type narrowing? Explain the different narrowing techniques.
Type narrowing refines a union type to a more specific type within a code block.
function process(value: string | number | null) {
// typeof narrowing
if (typeof value === 'string') {
console.log(value.toUpperCase()); // string here
}
// null/undefined check
if (value != null) {
console.log(value + 1); // string | number here
}
// instanceof narrowing
if (value instanceof Date) {
console.log(value.getFullYear()); // Date here
}
}
// 'in' narrowing
interface Circle { kind: 'circle'; radius: number; }
interface Square { kind: 'square'; side: number; }
type Shape = Circle | Square;
function getArea(shape: Shape) {
if ('radius' in shape) {
return Math.PI * shape.radius ** 2; // Circle here
}
return shape.side ** 2; // Square here
}
// Type predicate (custom type guard)
function isCircle(shape: Shape): shape is Circle {
return shape.kind === 'circle';
}
// Discriminated union — exhaustiveness check
function describeShape(shape: Shape): string {
switch (shape.kind) {
case 'circle': return `Circle r=${shape.radius}`;
case 'square': return `Square s=${shape.side}`;
default:
const _exhaustive: never = shape; // compile error if new shape added
return _exhaustive;
}
}
Q5. What are discriminated unions and why are they powerful?
Discriminated unions combine multiple types with a common literal property (the discriminant). They enable exhaustive pattern matching at compile time.
type ApiState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function render<T>(state: ApiState<T>) {
switch (state.status) {
case 'idle': return <EmptyState />;
case 'loading': return <Spinner />;
case 'success': return <DataView data={state.data} />; // T is available
case 'error': return <ErrorView message={state.error} />; // string is available
}
}
This pattern eliminates impossible states like { status: 'success', error: 'Something failed' } — which is the core benefit.
Q6. What are generics and how do you constrain them?
Generics enable writing reusable, type-safe code that works with multiple types.
// Basic generic
function identity<T>(value: T): T {
return value;
}
// With constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Aditya', email: '[email protected]' };
const name = getProperty(user, 'name'); // type: string
// getProperty(user, 'phone') — compile error!
// Multiple constraints
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// Generic with default
interface Paginated<T, Meta = { total: number; page: number }> {
data: T[];
meta: Meta;
}
// Conditional generic
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type StringResult = Unwrap<Promise<string>>; // string
type NumberResult = Unwrap<number>; // number (not wrapped)
Q7. What is the satisfies operator (TypeScript 4.9+)?
satisfies validates that a value matches a type without widening the inferred type.
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// Without satisfies, TypeScript would infer string | number[] for all values.
// With satisfies, each value retains its specific type:
const r = palette.red; // number[] — not string | number[]
const g = palette.green; // string — not string | number[]
// Compare to type annotation which widens the type:
const palette2: Record<string, string | number[]> = { red: [255, 0, 0] };
const r2 = palette2.red; // string | number[] — loses specific type info
Q8. What are template literal types?
TypeScript 4.1+ supports template literal types for powerful string manipulation.
type Direction = 'top' | 'right' | 'bottom' | 'left';
type CSSProperty = `margin-${Direction}` | `padding-${Direction}`;
// 'margin-top' | 'margin-right' | 'margin-bottom' | 'margin-left' | 'padding-...'
// Extract route params
type ExtractParams<Route extends string> =
Route extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<`/${Rest}`>
: Route extends `${string}:${infer Param}`
? Param
: never;
type RouteParams = ExtractParams<'/users/:userId/posts/:postId'>;
// 'userId' | 'postId'
// Event handler naming
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// API endpoint builder
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `${Lowercase<HttpMethod>}${Capitalize<string>}`;
Q9. What are mapped types?
Mapped types transform the properties of an existing type.
// Manual implementation of Partial
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Readonly with nested
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// Remapping keys with 'as'
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }
// Filtering properties by type
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface Mixed {
id: number;
name: string;
active: boolean;
count: number;
}
type StringProps = PickByValue<Mixed, string>; // { name: string }
type NumberProps = PickByValue<Mixed, number>; // { id: number; count: number }
Q10. What are conditional types and infer?
// Basic conditional type
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false
// Distributive conditional types
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Extract promise value
type AwaitedValue<T> = T extends Promise<infer R>
? R extends Promise<any>
? AwaitedValue<R>
: R
: T;
// infer in function types
type FirstArg<T> = T extends (arg: infer A, ...rest: any[]) => any ? A : never;
function doSomething(id: number, name: string): void {}
type FirstParam = FirstArg<typeof doSomething>; // number
// Conditional type for deep pick
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
Q11. What is declaration merging?
TypeScript merges multiple declarations with the same name. Useful for augmenting third-party types.
// Augmenting Express Request type
declare global {
namespace Express {
interface Request {
user?: {
id: string;
email: string;
role: 'admin' | 'user';
};
}
}
}
// Augmenting module types
declare module '*.svg' {
const content: string;
export default content;
}
declare module 'some-library' {
interface SomeInterface {
newProperty: string; // adds to existing interface
}
}
Q12. What is the difference between any, unknown, and never?
| Type | Description | Can assign to | Requires narrowing |
|---|---|---|---|
any | Opt-out of type system | Anything | No |
unknown | Type-safe unknown | Only unknown/any | Yes |
never | Empty type, never occurs | Nothing | N/A |
// unknown forces you to narrow before use
function processValue(value: unknown) {
// value.toUpperCase() — compile error!
if (typeof value === 'string') {
value.toUpperCase(); // safe
}
}
// never for exhaustiveness
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}
// never in unreachable code
function infiniteLoop(): never {
while (true) {}
}
Confident on Q1-Q12? You've cleared the TypeScript screening round. The intermediate section covers decorators, module augmentation, and advanced patterns — the topics that separate ₹12 LPA from ₹25 LPA+ offers.
INTERMEDIATE LEVEL — Proven Patterns That Get You Hired (Questions 13–28)
Q13. How do TypeScript decorators work? (TypeScript 5.x standard)
TypeScript 5.0 introduced the new standard ECMAScript decorator proposal, replacing the legacy experimentalDecorators.
// Class decorator
function sealed(target: typeof Base) {
Object.seal(target);
Object.seal(target.prototype);
}
// Method decorator
function log(
target: any,
context: ClassMethodDecoratorContext
) {
const methodName = String(context.name);
return function (this: any, ...args: any[]) {
console.log(`Calling ${methodName}(${args.join(', ')})`);
const result = target.call(this, ...args);
console.log(`${methodName} returned ${result}`);
return result;
};
}
class Calculator {
@log
add(a: number, b: number) {
return a + b;
}
}
// Property decorator for validation
function validate(min: number, max: number) {
return function(target: any, context: ClassFieldDecoratorContext) {
return function(this: any, value: number) {
if (value < min || value > max) {
throw new RangeError(`${String(context.name)} must be between ${min} and ${max}`);
}
return value;
};
};
}
Q14. Explain module augmentation and ambient declarations.
// ambient.d.ts — declare types for non-TypeScript files
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.css' {
const styles: { [className: string]: string };
export default styles;
}
// Global augmentation
declare global {
interface Window {
analytics: {
track(event: string, props?: Record<string, unknown>): void;
};
}
interface Array<T> {
first(): T | undefined;
last(): T | undefined;
}
}
// Module augmentation
import 'express';
declare module 'express-serve-static-core' {
interface Request {
requestId: string;
}
}
Q15. What is the keyof and typeof operator in types?
const config = {
host: 'localhost',
port: 3000,
debug: true,
} as const;
type Config = typeof config;
// { readonly host: "localhost"; readonly port: 3000; readonly debug: true }
type ConfigKey = keyof typeof config; // "host" | "port" | "debug"
// Combine to create safe config getter
function getConfig<K extends keyof typeof config>(key: K): typeof config[K] {
return config[key];
}
const port = getConfig('port'); // type: 3000 (literal, not number)
// getConfig('timeout') — compile error!
// Indexed access type
interface ApiResponse {
user: { id: number; name: string };
posts: { id: number; title: string }[];
}
type User = ApiResponse['user']; // { id: number; name: string }
type Post = ApiResponse['posts'][number]; // { id: number; title: string }
Q16. How do you write type-safe event emitters?
type EventMap = {
'user:login': { userId: string; timestamp: Date };
'user:logout': { userId: string };
'order:created': { orderId: string; amount: number };
'order:paid': { orderId: string };
};
class TypedEmitter<Events extends Record<string, unknown>> {
private listeners = new Map<keyof Events, Function[]>();
on<K extends keyof Events>(
event: K,
listener: (data: Events[K]) => void
): this {
const existing = this.listeners.get(event) ?? [];
this.listeners.set(event, [...existing, listener]);
return this;
}
emit<K extends keyof Events>(event: K, data: Events[K]): void {
this.listeners.get(event)?.forEach(fn => fn(data));
}
off<K extends keyof Events>(event: K, listener: Function): this {
const existing = this.listeners.get(event) ?? [];
this.listeners.set(event, existing.filter(fn => fn !== listener));
return this;
}
}
const emitter = new TypedEmitter<EventMap>();
emitter.on('user:login', ({ userId, timestamp }) => {
console.log(`${userId} logged in at ${timestamp}`);
});
// emitter.emit('user:login', { userId: '123' }) — compile error! Missing timestamp
emitter.emit('user:login', { userId: '123', timestamp: new Date() }); // OK
Q17. What are variadic tuple types (TypeScript 4.0+)?
// Prepend/append to tuple
type Prepend<T, Tuple extends unknown[]> = [T, ...Tuple];
type Append<Tuple extends unknown[], T> = [...Tuple, T];
type WithId<T extends unknown[]> = Prepend<string, T>;
type WithCallback<T extends unknown[]> = Append<T, () => void>;
type Args = [number, string];
type WithIdArgs = WithId<Args>; // [string, number, string]
// Spreading tuples in function signatures
function concat<T extends unknown[], U extends unknown[]>(
arr1: [...T],
arr2: [...U]
): [...T, ...U] {
return [...arr1, ...arr2];
}
const result = concat([1, 2], ['a', 'b']); // [number, number, string, string]
// Rest elements in middle of tuple
type MiddleRest = [string, ...number[], boolean];
const valid: MiddleRest = ['hello', 1, 2, 3, true];
Q18. How do you implement a type-safe API client?
// Define your API schema as types
interface ApiEndpoints {
'GET /users': {
query: { page?: number; limit?: number };
response: { users: User[]; total: number };
};
'GET /users/:id': {
params: { id: string };
response: User;
};
'POST /users': {
body: { name: string; email: string };
response: User;
};
'PUT /users/:id': {
params: { id: string };
body: Partial<Pick<User, 'name' | 'email'>>;
response: User;
};
}
type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
type EndpointKey = keyof ApiEndpoints;
async function apiCall<K extends EndpointKey>(
endpoint: K,
options: Omit<ApiEndpoints[K], 'response'>
): Promise<ApiEndpoints[K]['response']> {
const [method, path] = (endpoint as string).split(' ');
// resolve params in path, attach query/body...
const response = await fetch(path, {
method,
body: 'body' in options ? JSON.stringify(options.body) : undefined,
});
return response.json();
}
Q19. What is const type parameter (TypeScript 5.0)?
// Without const — infers mutable type
function inferType<T>(value: T): T {
return value;
}
const arr = inferType([1, 2, 3]); // type: number[]
// With const — infers readonly literal type
function inferConst<const T>(value: T): T {
return value;
}
const arr2 = inferConst([1, 2, 3]); // type: readonly [1, 2, 3]
const obj = inferConst({ x: 10, y: 20 }); // type: { readonly x: 10; readonly y: 20 }
// Useful for typed routes or configs passed to functions
function createRouter<const Routes extends readonly string[]>(routes: Routes) {
return routes;
}
const routes = createRouter(['/home', '/about', '/contact'] as const);
type Route = typeof routes[number]; // '/home' | '/about' | '/contact'
Q20. How do you type React components in TypeScript?
import React from 'react';
// Props with children
interface CardProps {
title: string;
variant?: 'primary' | 'secondary';
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
children: React.ReactNode;
}
const Card: React.FC<CardProps> = ({ title, variant = 'primary', onClick, children }) => (
<div className={`card card--${variant}`} onClick={onClick}>
<h2>{title}</h2>
{children}
</div>
);
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// forwardRef with TypeScript
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
(props, ref) => <input ref={ref} {...props} />
);
// useRef with proper typing
const ref = React.useRef<HTMLDivElement>(null);
// ref.current is HTMLDivElement | null
const mutableRef = React.useRef<number>(0);
// mutableRef.current is number (not null)
Q21. What are TypeScript project references?
Project references allow splitting a large TypeScript project into smaller composable pieces with independent compilation.
// tsconfig.json (root)
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" },
{ "path": "./packages/ui" }
]
}
// packages/ui/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "dist"
},
"references": [
{ "path": "../core" },
{ "path": "../utils" }
]
}
Build with tsc --build — only recompiles changed packages.
Q22–Q28: Quick Intermediate Round
Q22. What is as const?
Makes an expression deeply readonly with literal types inferred. ['north', 'south'] as const gives type readonly ['north', 'south'] instead of string[].
Q23. Explain the Awaited<T> utility type.
Recursively unwraps the type of a Promise. Awaited<Promise<Promise<string>>> gives string. Added in TypeScript 4.5.
Q24. What is structural vs nominal typing? TypeScript uses structural typing — two types are compatible if they have the same shape, regardless of name. Most other typed languages use nominal typing (same name required). You can simulate nominal typing with branded types.
Q25. What are branded/nominal types?
type UserId = string & { readonly __brand: unique symbol };
type OrderId = string & { readonly __brand: unique symbol };
// Now UserId and OrderId are not assignable to each other
function createUserId(id: string): UserId { return id as UserId; }
Q26. What is the difference between export type and export?
export type is a type-only export that is erased at compile time. Helps bundlers and type-strippers know they can safely remove the export without runtime impact.
Q27. What is noUncheckedIndexedAccess?
A compiler option that adds undefined to the type of any indexed access. arr[0] becomes T | undefined instead of T. Prevents many off-by-one errors.
Q28. How do you type async generators?
async function* paginate<T>(
fetchPage: (page: number) => Promise<T[]>
): AsyncGenerator<T, void, unknown> {
let page = 1;
while (true) {
const items = await fetchPage(page++);
if (items.length === 0) break;
yield* items;
}
}
Nailed Q1-Q28? You're already ahead of 80% of TypeScript candidates. The advanced section covers recursive types, type-safe builders, and real-world patterns — this is where ₹35 LPA+ offers are decided.
ADVANCED LEVEL — The Type Wizard Round (Questions 29–40)
Q29. How do you implement a recursive type-safe deep merge?
type DeepMerge<T, U> = {
[K in keyof T | keyof U]:
K extends keyof U
? K extends keyof T
? T[K] extends object
? U[K] extends object
? DeepMerge<T[K], U[K]>
: U[K]
: U[K]
: U[K]
: K extends keyof T
? T[K]
: never;
};
type A = { x: { a: number; b: string }; z: boolean };
type B = { x: { a: string; c: number }; y: string };
type Merged = DeepMerge<A, B>;
// { x: { a: string; b: string; c: number }; z: boolean; y: string }
Q30. How do you build a type-safe SQL query builder?
interface Tables {
users: { id: number; name: string; email: string; role: string };
posts: { id: number; title: string; userId: number; published: boolean };
}
type TableName = keyof Tables;
type TableRow<T extends TableName> = Tables[T];
type ColumnName<T extends TableName> = keyof TableRow<T>;
class QueryBuilder<T extends TableName> {
private tableName: T;
private selectedColumns: ColumnName<T>[] = [];
private whereClause: Partial<TableRow<T>> = {};
constructor(table: T) {
this.tableName = table;
}
select<K extends ColumnName<T>>(...cols: K[]): QueryBuilder<T> {
this.selectedColumns = cols;
return this;
}
where(conditions: Partial<TableRow<T>>): this {
this.whereClause = conditions;
return this;
}
build(): string {
const cols = this.selectedColumns.length
? this.selectedColumns.join(', ')
: '*';
const where = Object.entries(this.whereClause)
.map(([k, v]) => `${k} = ${JSON.stringify(v)}`)
.join(' AND ');
return `SELECT ${cols} FROM ${this.tableName}${where ? ` WHERE ${where}` : ''}`;
}
}
const query = new QueryBuilder('users')
.select('id', 'name', 'email')
.where({ role: 'admin' })
.build();
// SELECT id, name, email FROM users WHERE role = "admin"
Q31. Real-World Scenario: Type-safe Redux with TypeScript
// Define action creators with discriminated union
const increment = (amount: number) =>
({ type: 'counter/increment', payload: amount } as const);
const setUser = (user: User) =>
({ type: 'user/set', payload: user } as const);
type Action =
| ReturnType<typeof increment>
| ReturnType<typeof setUser>;
interface AppState {
counter: number;
user: User | null;
}
function reducer(state: AppState, action: Action): AppState {
switch (action.type) {
case 'counter/increment':
return { ...state, counter: state.counter + action.payload };
case 'user/set':
return { ...state, user: action.payload };
// TypeScript will error if a case is missing (with noImplicitReturns)
}
}
Q32. What is the infer keyword used for in advanced patterns?
// Extract return type of method from class
type MethodReturn<T, M extends keyof T> =
T[M] extends (...args: any[]) => infer R ? R : never;
class ApiService {
fetchUser(): Promise<User> { return fetch('/user').then(r => r.json()); }
fetchPosts(): Promise<Post[]> { return fetch('/posts').then(r => r.json()); }
}
type UserResult = MethodReturn<ApiService, 'fetchUser'>; // Promise<User>
// Unpack nested generic
type UnpackArray<T> = T extends (infer U)[] ? U : T;
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type UnpackBoth<T> = UnpackPromise<UnpackArray<T>>;
type Example = UnpackBoth<Promise<string>[]>; // string
Q33–Q40: Advanced Quick Fire
Q33. What is the NoInfer utility type (TypeScript 5.4)?
Prevents TypeScript from inferring a type parameter from a specific argument, forcing the developer to provide it explicitly or from another source.
Q34. How does TypeScript handle circular references in types? TypeScript allows recursive type aliases with some limitations. Recursive interface references are always valid. Recursive type aliases need a structural level of indirection (usually an object or array) to avoid infinite expansion.
Q35. What is PropertyKey?
A built-in type alias for string | number | symbol — valid keys for object properties.
Q36. What are opaque types / branded types used for in real applications?
Password hashing: HashedPassword vs PlainPassword. Currency: USD vs EUR. Primary keys: UserId vs OrderId. Prevents accidentally passing raw IDs where validated ones are expected.
Q37. Explain TypeScript module resolution strategies.
node16/nodenext (recommended for ESM): requires explicit .js extensions in imports. bundler: for Vite/webpack users — no extension requirements. classic: legacy, avoid. Configure with moduleResolution in tsconfig.
Q38. What are the performance implications of complex TypeScript types?
Deep recursive types, excessively distributed conditional types, and large union types can dramatically slow TypeScript's language server. Strategies: simplify unions, use interface extends instead of & intersections, pre-compute reused complex types.
Q39. What is the difference between override and regular method overriding?
The override keyword (TypeScript 4.3+) ensures you can only override methods that exist in the base class. Without it, a typo in the method name creates a new method silently. Enable with noImplicitOverride: true.
Q40. Real-World Scenario: Type-safe feature flags
type FeatureFlags = {
'dark-mode': boolean;
'new-checkout': boolean;
'ai-recommendations': { enabled: boolean; model: string };
};
function isFeatureEnabled<K extends keyof FeatureFlags>(
flags: FeatureFlags,
feature: K
): FeatureFlags[K] {
return flags[feature];
}
Common TypeScript Mistakes — Avoid These and Stand Out
- Using
anyexcessively — defeats the purpose; useunknownfor truly unknown values - Ignoring strictNullChecks — always enable
strict: truein tsconfig - Using
!non-null assertion carelessly — it suppresses errors at compile time, crashes at runtime interfacevstypeconfusion — useinterfacefor extensible shapes,typefor unions- Not using discriminated unions — leads to complex conditional logic that TypeScript can't check
- Casting with
asinstead of narrowing —asis an assertion that bypasses type safety - Forgetting
readonlyon function parameters — prevents accidental mutation - Exporting types from barrel files without
export type— breaks bundlers and isolatedModules
FAQ — Your TypeScript Career Questions, Answered
Q: Which TypeScript version should I target? TypeScript 5.4+ for new projects. Know the major features from 4.0 onwards as interviewers frequently ask about specific releases.
Q: Is learning TypeScript worth it for a frontend developer? Absolutely. TypeScript is now the default for React/Vue/Angular projects at any serious company. It's expected at mid-level and above.
Q: How strict should tsconfig be?
Enable "strict": true always. Optionally add noUncheckedIndexedAccess, noImplicitReturns, exactOptionalPropertyTypes.
Q: What is the fastest way to learn TypeScript generics?
Build a type-safe utility library. Try implementing Partial, Pick, Omit, ReturnType from scratch. Then solve TypeScript challenges at type-challenges.github.io.
Q: How do decorators differ between TypeScript 4.x and 5.x?
TypeScript 5.0 implements the finalized ECMAScript decorator proposal, which is fundamentally different from the legacy experimentalDecorators. The new decorators don't use emitDecoratorMetadata and have different parameter signatures.
Q: Is TypeScript needed for Node.js backends? Yes, most Node.js projects at product companies use TypeScript. Fastify, NestJS, and tRPC are built with TypeScript first.
Q: What is tRPC and why does TypeScript matter for it?
tRPC enables end-to-end type safety between your backend API and frontend without code generation. TypeScript generics power the type inference that makes this possible.
Q: How do you handle TypeScript in a monorepo?
Use project references with composite: true, path aliases in tsconfig, and a shared tsconfig.base.json. Tools: Turborepo, Nx, or pnpm workspaces.
Practice Resources: TypeScript Playground (typescriptlang.org/play), Type Challenges (github.com/type-challenges), Official Docs Handbook.
TypeScript mastery is one of the highest-ROI skills you can build. It makes you better at React, Node.js, and even Go/Rust (type thinking transfers). Bookmark this, solve type challenges daily, and watch your interview confidence skyrocket.
Related Articles:
- React Interview Questions 2026 — React + TypeScript is the default frontend stack
- Golang Interview Questions 2026 — static typing transfers between Go and TS
- Microservices Interview Questions 2026 — type-safe APIs with tRPC
- Docker Interview Questions 2026 — containerize your TypeScript apps
- Blockchain & Web3 Interview Questions 2026 — Web3 frontends use TypeScript + wagmi
Explore this topic cluster
More resources in Interview Questions
Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.
Related Articles
Top 35 Blockchain & Web3 Interview Questions 2026 — Complete Guide with Solutions
Web3 engineers are among the highest-paid developers globally. Mid-level Solidity devs earn ₹20-40 LPA in India; senior...
Top 40 Cybersecurity Interview Questions 2026 — Complete Guide with Solutions
Cybersecurity is the highest-demand, highest-paying field in tech right now. AppSec Engineers pull ₹15-35 LPA, Cloud...
Top 40 Go (Golang) Interview Questions 2026 — Complete Guide with Solutions
Go developers are among the highest-paid backend engineers in India. Mid-level Go engineers earn ₹18-35 LPA at Series B+...
Top 50 React Interview Questions 2026 — Complete Guide with Solutions
React developers are the most in-demand frontend engineers in India. Mid-level React roles pay ₹15-30 LPA at product...
AI/ML Interview Questions 2026 — Top 50 Questions with Answers
AI/ML engineer is the highest-paid engineering role in 2026, with median compensation exceeding $200K at top companies. But...