75 TypeScript interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
TypeScript is a superset of JavaScript that adds a static type system, so many mistakes surface at compile time rather than at runtime. The questions cover basic types, interface versus type alias, generics, utility types such as Partial and Required, type guards and narrowing, discriminated unions, enums versus string unions, strictNullChecks, satisfies, and typing API data.
and 11 more topics.
TypeScript is a statically typed superset of JavaScript that compiles to plain JavaScript. The same code in JavaScript runs happily and returns '102' instead of 12, because 10 + '2' concatenates strings. No error is thrown; it only shows up when a user sees the…
A type annotation is the : Type you write to state the type of a variable, parameter or return value explicitly, e.g. let name: string = 'John'. Type inference is TypeScript working out the type from the assigned value. let count = 0 is already…
Three types sitting at opposite ends of the type system. Accepts Assignable to others Safe ------------ any everything everything no unknown everything only after narrowing yes never nothing everything — The practical use of never — exhaustiveness checking, so adding a case and forgetting to…
A B means or, A & B means and. The common confusion — unions narrow what you may use, intersections widen it: To reach a union member's own property you must narrow first (typeof, in, a discriminated union). Two intersection traits interviewers probe: Practical pitfall:…
A literal type accepts exactly one specific value. They replace enum without emitting runtime code: Note — TypeScript widens the type when you use let: Keep the literal with as const: Takeaway: literal types underpin discriminated unions and narrowing — the two most valuable tools…
Type narrowing refines the type of a variable within a code branch, based on an earlier check. TypeScript narrows automatically in if/else, switch, the ternary operator, and after an early return. These five techniques fit five situations: typeof for primitives, instanceof for class instances, in…
as overrides TypeScript's inference at compile time and checks nothing at runtime. Two legitimate uses: 1. The DOM — TypeScript only knows HTMLElement, you know something more specific. 2. as const — preserving literal types; the safest form of assertion. Two cases where it is…
! tells TypeScript a value is definitely not null/undefined — and, again, checks nothing at runtime. The alternatives, safest first: There is a second form of ! — the definite assignment assertion on a property: Common with dependency injection, and also exactly where strictPropertyInitialization gets…
A tuple is an array with a fixed number of elements and a fixed type at each position. Elements can have names, can be optional (?), or can be a rest element (...). The most common use is a function returning several values, like minMax…
Enums declare a set of named constants. Numeric enums auto-assign 0, 1, 2... and support reverse lookup (Direction[0] returns 'Up'), but reverse lookup is a common source of confusion because the enum object holds both keys and values. String enums require explicit values, are easier…
satisfies (since TypeScript 4.9) checks that a value matches a type, but does not change the inferred type of that value. You get the compiler's error checking and keep the specific type for later use. What happens in each case: - Annotation : Config: after…
as const (a const assertion) tells TypeScript to infer the narrowest type for a value and mark everything inside it readonly: strings keep their literal, arrays become readonly tuples, and object properties become readonly. Without as const, roles is just string[]: TypeScript widens the literals…
"strict": true enables a family of flags at once: The two with the biggest impact: useUnknownInCatchVariables (TS 4.4) is the one that confuses people upgrading older projects: Migrating a JavaScript project means enabling them gradually, not all at once: Takeaway: new projects should start with…
Both describe object shapes; they differ in what they can express beyond objects and whether declarations merge. interface type --------- Describes objects yes yes Union / tuple / conditional no yes Declaration merging yes no Extension extends & Error messages usually terser can expand at…
Interfaces use interface Child extends Parent { ... }, and extend several at once with extends A, B. Types use an intersection: type Combined = A & B. An interface can extend a type and vice versa. The difference shows up when a property has…
An index signature lets an object have keys that are not known when you write the code: [key: string]: string means "every string key has a string value". Why length: number fails: the index signature applies to every string key, and length is one of…
An optional property (prop?: Type) is a property that may be missing from the object. A readonly property can only be assigned when the object is created; assigning it again later is a TypeScript error. - Optional: reading user.nickname gives string undefined, so check it…
A discriminated union is a union where each member has a common literal property (the discriminant). TypeScript automatically narrows the type when checking the discriminant. Inside each case, shape is already narrowed, so accessing shape.radius or shape.side is safe. If you add a new member,…
A recursive type is a type that references itself, used for data whose structure repeats at every level, such as trees, nested JSON and linked lists. The type has to mention itself because a child has the same shape as its parent. An interface can…
Excess property checking is an extra check TypeScript applies only to "fresh" object literals, meaning an object written directly at the assignment or at the argument position. If that literal has a property the target type does not declare, TypeScript reports an error. Assigning through…
Generics allow creating components (functions, classes, interfaces) that work with multiple types while remaining type-safe. Instead of using any, use a type parameter <T. Reuse code without losing type information.
A constraint (extends) limits a type parameter: it only accepts types that satisfy a given condition. Inside the function you can then use the properties the condition guarantees, and callers passing a type that does not satisfy it get a compile-time error. For getProperty, K…
keyof T returns a union type of all keys of T: keyof {a: number, b: string} gives 'a' 'b'. Combined with generics, it allows property access where TypeScript still checks both the key and the return type: K extends keyof T forces key to be…
Two utility types that extract a parameter list as a tuple. Both are built on infer: The most practical use — wrapping a function without restating its signature: Pick a single parameter by index: Note: for overloaded functions, Parameters<T only picks up the last signature,…
A generic function can declare several type parameters inside <..., separated by commas. At the call site TypeScript infers each one from the matching argument, and one type parameter can be constrained by another using extends. What happens in the two examples: - zip: T…
Partial<T makes every property of T optional (adds ?). Required<T does the opposite: it removes ? so every property is required. Both change only the top level (shallow). If User also had address: { city: string; street: string }, then Partial<User lets you omit address,…
Record<Keys, Value creates an object type whose keys come from Keys and whose values all have type Value. Unlike an index signature, Keys can be a specific union: then the object must have every key, none is optional, and unknown keys are not allowed. With…
Pick<T, K creates a type that keeps only the properties K from T. Omit<T, K does the opposite: it keeps every property of T except K. Both are common for DTOs, shaping API responses and form state when you only need part of a type.…
Both are utility types that filter the members of a union type: - Exclude<T, U: removes from T the members assignable to U. - Extract<T, U: keeps only the members of T assignable to U. The mechanism is in their definitions: Exclude<T, U is T…
Both let you derive types from existing code instead of redeclaring them by hand: - ReturnType<T: gets the return type of a function type T. - Awaited<T: "unwraps" a Promise to get the type of the value inside, recursively for nested Promises: Awaited<Promise<Promise<number is number.…
NonNullable<T is a utility type that removes null and undefined from a type. For example, NonNullable<string null undefined gives string. It is equivalent to Exclude<T, null undefined, and it only matters with strictNullChecks on (included in strict), because with that flag off null already fits…
Both prevent changes to data at the type level: - Readonly<T: marks every property of the object type T as readonly. Reassigning a property is an error. - ReadonlyArray<T (shorthand readonly T[]): an array without the mutating methods like push, pop, splice, sort, and no…
Partial<T makes every property of T optional. Rebuilding it takes one mapped type: iterate over each key of T and produce a new object type. Read [K in keyof T]?: T[K] piece by piece: - keyof T: the union of T's keys. For User that…
A type guard is a runtime check that TypeScript understands, so inside the if branch it narrows the variable's type. The common kinds: - typeof: for primitives (string, number, boolean...). Since typeof null is 'object', an object check needs !== null. - instanceof: for objects…
A user-defined type guard is a function that returns a boolean but declares a special return type param is Type (a type predicate). When it returns true, TypeScript treats that parameter as Type and narrows it inside the if branch; the else branch gets the…
A declaration file (.d.ts) contains only type declarations, no runnable code: function names, parameters, return types, object shapes. It emits no JavaScript at build time. It is needed because a lot of what TS code uses has no types of its own: libraries written in…
The options interviewers ask about decide three things: how strict type checking is, what JavaScript is emitted, and how imports are resolved. A typical tsconfig.json for an app built with a bundler (Vite, Next.js): - strict: the most important one. It turns on a family…
Most hooks infer their type from the initial value, so you only add a generic when that value does not carry enough information: - useState: use useState<Type(init) when init does not describe the full type, typically null before data arrives. - useRef: useRef<HTMLInputElement(null) for DOM…
React wraps DOM events in synthetic events, with a separate type for each kind: React.ChangeEvent<T for onChange, React.MouseEvent<T for onClick, React.SubmitEvent<T for onSubmit. The generic parameter T is the type of the element the handler is attached to, so event.currentTarget has the right type (HTMLInputElement…
paths in tsconfig.json declares aliases for import paths, for example @/components/Button instead of ../../../components/Button. But paths only helps TypeScript find files for type checking: when compiling, tsc leaves the import string unchanged in the emitted JavaScript. So whatever runs or bundles the code (Vite, webpack,…
Both describe object shapes and are nearly interchangeable, so pick based on each one's unique capability, not taste. Use interface when: - Describing an object/class contract that others may extend — interface supports declaration merging and extends. - Exposing a library's public API: consumers can…
With strictNullChecks off, null and undefined are assignable to every type. Turning it on splits them into their own types, so the compiler starts flagging places that were already runtime hazards. Three common error groups: 1. Object is possibly 'null' — DOM lookups or find()…
In modern frontend code, a union of string literals is usually the default; enum stays only when you need a real runtime value namespace. Why unions win: - No runtime code emitted. A (non-const) enum compiles to a JavaScript object that ships in the bundle.…
res.json() returns Promise<any, and as User performs no runtime check — it just tells the compiler "trust me". If the backend renames a field or returns an error body, the code still compiles cleanly and breaks somewhere far from the cause. Three levels of handling,…
Use a discriminated union instead of one object with many optional fields. The union shares a discriminant field (usually status) so the compiler knows exactly which fields exist in each branch. Why this beats { isLoading, data?, error? }: that shape permits meaningless combinations such…
TypeScript narrows via control flow analysis. For a mutable property, the compiler keeps the narrowed result only while it can be sure nothing reassigns it. A plain function call does not drop property narrowing (TypeScript accepts that risk for convenience). The real point where it…
They check at three different strengths, and crucially they retain different amounts of type information. - : Palette (annotation) — checks correctly, but the variable is widened to the declared type. a.green.toUpperCase() fails because the compiler only knows string [number, number, number]. - as Palette…
Because of literal widening. When declared with let or placed inside an object literal, TypeScript widens the literal type 'GET' to string — that value could be reassigned. string is not assignable to a narrow union. Fixes: - as const on the object or the…
K extends keyof T is a generic constraint — it restricts the type parameter K to real keys of T, which both rejects bad keys at compile time and lets the return type describe the exact sub-object. Without the constraint: - If K is string,…
TypeScript widens a literal to string when T has no constraint and does not appear bare in the return type — e.g. a function returning { v } or T[]. Returning a bare T keeps the literal. To keep literals in every case, add a…
A mapped type iterates a type's keys to build a new type; the as clause (TS 4.1) lets you rename keys during iteration, so you can transform names, not just values. Filter keys by remapping to never — any key mapped to never is dropped…