TypeScript is a statically typed superset of JavaScript developed by Microsoft. These questions cover core concepts from basic types and interfaces to advanced generics, decorators, and compiler configuration — commonly asked in frontend, backend, and full-stack engineering interviews.
TypeScript is a statically typed superset of JavaScript that compiles down to plain JavaScript. It adds optional static typing, interfaces, generics, and modern language features, enabling better tooling, earlier error detection, and improved code maintainability compared to vanilla JavaScript.
TypeScript's basic primitive types are string, number, boolean, null, undefined, symbol, and bigint. These mirror JavaScript primitives but allow the compiler to enforce correct usage at compile time.
Both can describe object shapes, but interfaces support declaration merging (multiple declarations are merged) and are generally preferred for object/class contracts. Type aliases are more flexible — they can represent unions, intersections, tuples, and primitives — but cannot be merged after declaration.
The any type disables all type checking for a variable, effectively opting it out of TypeScript's type system. It should be avoided because it defeats the purpose of using TypeScript and can hide runtime bugs; prefer unknown for unknown values as it still enforces type checks before use.
Both can hold any value, but unknown is type-safe: you must narrow or assert the type before performing operations on it. any bypasses all type checks entirely, while unknown forces explicit handling, making it the safer alternative when the type is truly not known.
A union type (A | B) means a value can be one of several types, while an intersection type (A & B) means a value must satisfy all combined types simultaneously. Union types are useful for flexible parameters; intersection types are used to merge multiple type shapes together.
Type narrowing is the process of refining a broad type to a more specific one within a code block. Common techniques include typeof guards, instanceof checks, equality narrowing, the in operator, and user-defined type guards (predicates with the 'is' keyword).
Generics allow you to write reusable, type-safe code that works with multiple types without sacrificing type information. For example, function identity<T>(arg: T): T returns the same type it receives, enabling strongly typed reuse across many data types.
readonly prevents a property from being reassigned after initialization, similar to const but for object properties. It is useful for immutable data structures, value objects, and preventing accidental mutation in function parameters.
Decorators are special declarations (prefixed with @) that can be attached to classes, methods, properties, or parameters to add metadata or modify behavior at design time. They are widely used in frameworks like Angular and NestJS for dependency injection, routing, and validation.
Declaration merging automatically combines multiple interface declarations with the same name into one, useful for augmenting third-party types. Extending (interface B extends A) creates a new interface that inherits members from another, which is explicit and does not affect the original interface.
Mapped types create new types by transforming each property of an existing type. For example, type Readonly<T> = { readonly [K in keyof T]: T[K] } makes every property of T readonly. Built-in examples include Partial<T>, Required<T>, Record<K,V>, and Pick<T,K>.
Conditional types use the syntax T extends U ? X : Y to select a type based on a condition, similar to ternary operators. They are powerful for type-level logic, for example: type NonNullable<T> = T extends null | undefined ? never : T removes null and undefined from a type.
The infer keyword is used inside conditional types to declare a type variable that TypeScript infers from the matched type. For example, type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never extracts the return type of a function type.
void means a function returns no meaningful value (implicitly returns undefined), while never means a function never returns at all — either it throws an error or runs forever. never is also the result of impossible types in conditional types (like string & number).
Template literal types combine string literal types using template literal syntax to produce new string types. For example, type EventName<T extends string> = `on${Capitalize<T>}` produces types like 'onClick' from 'click', enabling precise string-based APIs.
Module augmentation allows you to add new declarations to existing modules or global scope without modifying original source files. You wrap new declarations inside declare module 'module-name' {} to safely extend third-party library types, commonly used to augment Express Request or Vue's ComponentCustomProperties.
tsconfig.json configures the TypeScript compiler (tsc) for a project, specifying target ECMAScript version, module system, strictness, and included/excluded files. Key options include strict (enables all strict checks), target (output JS version), moduleResolution, paths (for aliases), and outDir.
The strict flag enables a suite of strictness checks: strictNullChecks (null/undefined are not assignable to other types), noImplicitAny, strictFunctionTypes, strictPropertyInitialization, and others. Enabling strict is considered best practice as it catches the most common categories of runtime bugs at compile time.
Utility types are built-in generic type helpers that transform existing types. Common examples include Partial<T> (all properties optional), Required<T>, Readonly<T>, Pick<T,K> (select keys), Omit<T,K> (exclude keys), Record<K,V>, Exclude<T,U>, Extract<T,U>, NonNullable<T>, ReturnType<T>, and Parameters<T>.
© RM Full Stack & AI Engineer · All interview questions · Roadmaps · Open the app