· development · 17 min read
The 45 TypeScript interview questions — interview cheat sheet I wish I had
Whether you're preparing for a frontend interview or brushing up on TypeScript fundamentals, these 45 questions — from basic type aliases to advanced conditional inference patterns — cover the concepts that interviewers actually ask about in 2026.

Why I wrote this
I’ve been on both sides of the TypeScript interview table — sweating through questions I should have known, and watching candidates stumble over the same gaps I once had. After going through a recent round of interviews (and helping a few friends prepare for theirs), I realized something: most “TypeScript interview question” lists out there are either too shallow or too academic. They either stop at interface vs type or jump straight into type-level gymnastics that nobody uses in production.
So I sat down and compiled 45 questions that actually come up — split into basic, intermediate, and advanced tiers. Each answer is concise, includes a code example, and focuses on the “why” rather than textbook definitions. This isn’t a cheat sheet to memorize; it’s a study guide to genuinely understand the language.
Whether you’re a junior developer preparing for your first TypeScript role, a senior engineer refreshing before a FAANG loop, or a hiring manager looking for a solid question bank — I hope this saves you some time.
Basic Questions
1. What’s the difference between interface and type?
Interfaces are primarily for describing object shapes and support extension and declaration merging; type aliases are more general and can represent unions, intersections, primitives, etc.
interface Animal { name: string }
interface Animal { age: number } // merges → { name; age }
interface Dog extends Animal { breed: string }
type ID = string | number // unions (interface can't)
type Point = readonly [number, number] // tuples
type Cat = Animal & { breed: string } // intersections2. What does the any type do and why should you avoid it?
any disables type checking for that value, so you lose all compile‑time safety. It’s usually better to use unknown or a more precise type.
let data: any = "hello";
data.nonExistentMethod(); // No error in TS, but will crash at runtime3. What’s the difference between unknown and any?
unknown forces you to narrow or assert the type before using it, while any lets you do anything without checks.
let a: unknown = "hi";
a.toUpperCase(); // Error: object is of type 'unknown'
let b: any = "hi";
b.toUpperCase(); // OK, but unsafe4. What are union and intersection types?
Union types (|) describe values that can be one of several types; intersection types (&) combine multiple types into one that must satisfy all of them.
type ID = string | number; // Union
type Entity = { id: number } & { name: string }; // Intersection5. What is type inference?
TypeScript automatically infers types from usage when you don’t annotate them explicitly, often giving you strong types with less boilerplate.
let x = 5; // inferred as number
let arr = [1, 2, 3]; // inferred as number[]6. What’s the difference between interface extending and type intersection?
Both can build on existing shapes, but interfaces support extends and declaration merging, while type intersections work with any types, not just object shapes.
interface A { x: number }
interface B extends A { y: number }
type C = { x: number };
type D = C & { y: number };7. What are optional properties?
Optional properties (with ?) may be omitted when creating an object; when present, they must match the declared type.
interface User {
name: string;
email?: string; // Optional
}
const u: User = { name: "Tomas" }; // Valid8. What are arrays and tuples in TypeScript?
Arrays typically store many values of the same element type, while tuples have a fixed length with specific types at each index.
let nums: number[] = [1, 2, 3]; // typically homogeneous
let tuple: [string, number] = ["age", 30];
// tuple[0] is string, tuple [oneuptime](https://oneuptime.com/blog/post/2026-02-20-typescript-strict-mode-guide/view) is number9. What is the as keyword for type assertions?
Type assertions tell TypeScript to treat a value as a more specific type; they don’t change the runtime value, so they should be used sparingly and only when you’re sure.
let value: unknown = "hello";
let len = (value as string).length; // Assert it's a string
// Double assertion pattern (should be rare)
let el = value as unknown as HTMLElement;10. What are enums and when should you use them?
Enums define a set of named constants. They can be useful when interoperating with existing codebases, but union string literals are often preferred in modern TypeScript for simplicity and better tooling.
enum Direction { Up, Down, Left, Right }
let d: Direction = Direction.Up;
// Often preferred: union literals
type Dir = "up" | "down" | "left" | "right";11. What’s the difference between null and undefined?
undefined typically means “not initialized” or “missing”, while null means “intentional empty value”. With strictNullChecks (part of "strict": true), null and undefined are only assignable when explicitly included in the type.
let a: undefined = undefined;
let b: null = null;
let name: string = null; // Error when strictNullChecks is enabled12. What is the ! non-null assertion operator?
The ! operator tells TypeScript that a value is definitely not null or undefined at that point, bypassing checks. It’s useful when the type system can’t see a guarantee that you know exists, but overuse hides real bugs.
function getInput(): string | undefined {
/* ... */
}
const value = getInput()!; // Asserts it's not undefined13. What is declaration merging?
When you declare multiple interfaces or namespaces with the same name, TypeScript merges them into a single definition.
interface Config { debug: boolean }
interface Config { logLevel: string }
// Merged: { debug: boolean; logLevel: string }14. What are literal types?
Literal types represent specific values (like "left" or 42), allowing you to express narrow, exact types instead of broad ones like string or number.
type Direction = "left" | "right";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
let dir: Direction = "left"; // Only "left" or "right" allowed15. What’s the difference between tsc and ts-node?
tsc is the TypeScript compiler that emits JavaScript files; ts-node runs TypeScript directly by compiling in memory, which is convenient for scripts or development.
tsc app.ts # Outputs app.js
ts-node app.ts # Runs TypeScript directly without emitting a fileIntermediate Questions
1. What are generic types?
Generics let you write reusable components that work with multiple types while preserving type information.
function identity<T>(arg: T): T {
return arg;
}
identity<string>("hello"); // T is string
identity(42); // T inferred as number2. What is typeof in TypeScript?
typeof in type positions creates a type from the static type of a value, useful for keeping types in sync with actual objects.
const config = { host: "localhost", port: 3000 };
type Config = typeof config; // { host: string; port: number }3. What are type guards?
Type guards are runtime checks (like typeof, instanceof, or custom predicates) that let TypeScript narrow a value’s type within a code branch.
function process(x: string | number) {
if (typeof x === "string") {
return x.toUpperCase(); // x is string here
}
return x.toFixed(2); // x is number here
}4. What’s the difference between readonly and const?
const applies to variables and prevents rebinding, while readonly is a type‑level modifier on properties/indices that prevents writing to that property but not rebinding the variable itself.
const arr = [1, 2];
arr.push(3); // OK - array contents mutated, binding not changed
const readonlyArr: readonly number[] = [1, 2];
readonlyArr.push(3); // Error - cannot modify readonly array5. What is the keyof operator?
keyof produces a union of the property names of a type; combined with generics, it lets you write type‑safe property access helpers.
interface User { id: number; name: string }
type Keys = keyof User; // "id" | "name"
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}6. What are mapped types?
Mapped types transform all properties of another type according to some rule, such as making them readonly or nullable. (TypeScript provides built‑ins like Readonly and Partial that use this pattern.)
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
interface Person { name: string; age: number }
type ReadonlyPerson = Readonly<Person>;7. What are conditional types?
Conditional types choose one type or another based on a relationship between types, enabling powerful type‑level logic.
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>; // true
type B = IsArray<string>; // false8. What are index signatures?
Index signatures describe types of properties accessed via dynamic keys (like dictionaries or maps).
interface StringMap {
[key: string]: string;
}
const map: StringMap = {
name: "Tomas",
role: "engineer",
};9. What is the satisfies operator?
satisfies checks that a value conforms to a type but keeps the value’s most specific inferred type instead of widening it.
type Colors = Record<string, string | { hex: string }>;
const theme = {
primary: "blue",
secondary: { hex: "#ff0000" },
} satisfies Colors;
// theme.primary is still inferred as "blue", not string,
// so literal-based behavior and autocomplete remain precise.
theme.primary.toUpperCase(); // OK10. What are utility types?
Utility types are built‑in mapped/conditional types (like Partial, Pick, Omit, Readonly, Required) that perform common transformations on object types.
interface User { id: number; name: string; email: string }
type PartialUser = Partial<User>; // All properties optional
type NameOnly = Pick<User, "name">; // { name: string }
type WithoutEmail = Omit<User, "email">; // { id: number; name: string }
type RequiredUser = Required<PartialUser>; // All required11. What is the as const assertion?
as const tells TypeScript to infer the most specific, readonly literal types from a value, turning arrays into readonly tuples and object properties into readonly literal types.
const colors = ["red", "blue"] as const;
// Type: readonly ["red", "blue"]
const config = { mode: "production" } as const;
// Type: { readonly mode: "production" }12. What is type narrowing?
Type narrowing is the process by which TypeScript refines a value’s type inside control‑flow branches based on checks you perform.
function handle(x: string | number | boolean) {
if (typeof x === "string") {
x.toUpperCase(); // x: string
} else if (typeof x === "number") {
x.toFixed(2); // x: number
} else {
x.valueOf(); // x: boolean
}
}13. What are discriminated unions?
Discriminated unions use a common literal field (the discriminant) to represent different variants, making it easy to narrow via switch or if checks.
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
}
}14. What is the this type?
The this type refers to the type of the current instance inside a class or object, which is especially useful for fluent APIs that return this for chaining.
class Builder {
private value = "";
add(s: string): this {
this.value += s;
return this; // Enables method chaining with correct subtype
}
}
const b = new Builder().add("a").add("b");15. What are default generic parameters?
Default generic parameters let you specify a fallback type when the caller doesn’t provide one.
interface ApiResponse<T = unknown> {
data: T;
status: number;
}
const res1: ApiResponse = { data: "test", status: 200 }; // T = unknown (data: unknown)
const res2: ApiResponse<string> = { data: "test", status: 200 }; // T = stringAdvanced Questions
1. What are template literal types?
Template literal types build new string literal types from other string unions, similar to how template strings build runtime strings.
type EventName<T extends string> = `on${Capitalize<T>}`;
type Click = EventName<"click">; // "onClick"
type Color = "red" | "blue";
type Size = "sm" | "lg";
type Class = `${Color}-${Size}`;
// "red-sm" | "red-lg" | "blue-sm" | "blue-lg"2. What is the infer keyword?
infer is used inside conditional types to capture and name part of a type (like element type, return type, or parameter tuple) for reuse.
type UnwrapArray<T> = T extends (infer U)[] ? U : T;
type A = UnwrapArray<string[]>; // string
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type B = UnwrapPromise<Promise<number>>; // number
type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;
type C = GetReturn<() => string>; // string3. What are covariance and contravariance?
Covariance means subtypes are allowed where supertypes are expected (e.g. return types, arrays); contravariance means the direction is reversed for function parameter types.
type Animal = { name: string };
type Dog = Animal & { breed: string };
// Covariance - arrays
let dogs: Dog[] = [{ name: "Rex", breed: "Lab" }];
let animals: Animal[] = dogs; // OK
// Contravariance - function parameters (with strictFunctionTypes)
type Handler<T> = (item: T) => void;
let handleDog: Handler<Dog> = (d) => console.log(d.breed);
let handleAnimal: Handler<Animal> = handleDog; // Error in strict mode - unsafe4. What’s the difference between never and void?
void indicates a function returns no meaningful value, while never indicates a function never returns (throws or loops forever) or a type that can’t occur. never is also used for exhaustiveness checks.
function log(msg: string): void {
console.log(msg);
}
function fail(msg: string): never {
throw new Error(msg);
}
type Shape = "circle" | "square";
function getArea(s: Shape): number {
switch (s) {
case "circle": return 3.14;
case "square": return 1;
default: {
const _exhaustive: never = s; // Error if a case is missing
return _exhaustive;
}
}
}5. How do you type a function with different parameter types?
Use function overloads: multiple signatures for the same implementation, giving call‑site‑specific return types.
function convert(input: string): number;
function convert(input: number): string;
function convert(input: string | number): string | number {
return typeof input === "string" ? Number(input) : String(input);
}
const a: number = convert("42"); // inferred number
const b: string = convert(42); // inferred string6. What are branded types?
Branded types add a phantom field to create nominal‑like distinctions between structurally identical types.
type UserId = string & { __brand: "UserId" };
type OrderId = string & { __brand: "OrderId" };
function createUser(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId): void { /* ... */ }
function getOrder(id: OrderId): void { /* ... */ }
const uid = createUser("123");
getUser(uid); // OK
getOrder(uid); // Error: UserId not assignable to OrderId7. What are recursive types?
Recursive types refer to themselves, allowing you to model trees, nested JSON, deeply readonly structures, etc. Note that using extends object will include arrays and functions as well.
type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
interface JSONObject { [key: string]: JSONValue }
interface JSONArray extends Array<JSONValue> {}
type DeepReadonly<T> = {
readonly [K in keyof T]:
T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Node {
value: number;
left?: Node;
right?: Node;
}8. What is unknown in catch clauses?
By default, untyped catch variables are any, but since TypeScript 4.4 you can enable the useUnknownInCatchVariables compiler option so they default to unknown, forcing you to narrow before using them. typescriptlang
try {
throw new Error("failed");
} catch (err) {
// With useUnknownInCatchVariables, 'err' is unknown here
if (err instanceof Error) {
console.log(err.message); // Safe
}
}
// You can still explicitly opt out:
try {
throw new Error("failed");
} catch (err: any) {
console.log(err.message); // 'any' again
}9. What are variadic tuple types?
Variadic tuple types let you express tuple operations like appending, concatenating, or extracting parts using ... in type positions.
type Push<T extends unknown[], V> = [...T, V];
type A = Push<[1, 2], 3>; // [1, 2, 3]
type Concat<A extends unknown[], B extends unknown[]> = [...A, ...B];
type B = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
type First<T extends unknown[]> = T extends [infer F, ...unknown[]] ? F : never;
type C = First<[1, 2, 3]>; // 110. What are mapped type modifiers?
Mapped type modifiers add or remove readonly and optional (?) modifiers from properties. TypeScript’s built‑ins like Required and Readonly use this pattern.
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // Remove readonly
type AllRequired<T> = { [K in keyof T]-?: T[K] }; // Remove optional
interface ReadonlyUser {
readonly id: number;
name?: string;
}
type WritableUser = Mutable<ReadonlyUser>; // id is writable
type CompleteUser = AllRequired<ReadonlyUser>; // name is required11. What’s the difference between private and # private fields?
private is enforced only by TypeScript’s type system and is erased or transformed at compile time; # private fields are a JavaScript feature that enforces privacy at runtime and require a compatible target or down‑emit. typescriptlang
class Secret {
private hidden = "ts-private"; // TS-only privacy
#reallyHidden = "js-private"; // Runtime-enforced privacy
}
const s = new Secret();
// s.hidden; // Compile-time error, but exists at runtime
// s.#reallyHidden; // Syntax error, cannot access12. What are declaration files and the declare keyword?
Declaration files (.d.ts) describe the shape of existing JS code without implementations. The declare keyword tells TypeScript “this exists at runtime elsewhere”.
// types/my-lib.d.ts
declare function myLib(config: { debug: boolean }): void;
declare const version: string;
declare class MyLib {
constructor(name: string);
doSomething(): void;
}
// Ambient module declaration
declare module "untyped-lib" {
export function init(): void;
export const name: string;
}13. What are conditional type inference patterns?
These are reusable conditional types that extract parts of other types, like function parameters, instance types, or deeply nested array element types. (TypeScript ships built‑ins Parameters and InstanceType similar to these.)
// Extract function parameters
type ParametersOf<T> = T extends (...args: infer P) => any ? P : never;
type Params = ParametersOf<(a: string, b: number) => void>; // [string, number]
// Extract instance type
type InstanceTypeOf<T> = T extends new (...args: any[]) => infer R ? R : never;
type Inst = InstanceTypeOf<typeof Date>; // Date
// Flatten array recursively
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type Flat = Flatten<number[][][]>; // number14. What is the unique symbol type?
unique symbol is a subtype of symbol representing a single, specific symbol value. It’s only allowed on const declarations and readonly static properties, and you usually refer to it via typeof in types. typescriptlang
const sym: unique symbol = Symbol();
interface Obj {
[sym]: string; // Only this exact symbol can be used as the key
}
const brand: unique symbol = Symbol();
type Branded<T, B> = T & { [brand]: B };
type SafeString = Branded<string, "Safe">;
// Referencing a specific unique symbol type
type BrandType = typeof brand;15. What are type-only imports and exports?
Type‑only imports/exports (import type / export type) import or export only the type information with no runtime code, which helps avoid circular dependencies and reduce bundle size.
import type { User, Config } from "./types"; // Only types, erased at compile time
export type { User, Config };
// Mixing value and type imports
import { api, type User as ApiUser } from "./api";
// api is a runtime value, ApiUser is a type-only aliasConclusion
If you’ve made it through all 45 questions, you now have a solid mental map of TypeScript — from the fundamentals that trip up juniors to the advanced type machinery that separates senior candidates from the rest. The key takeaway isn’t to memorize these answers word for word. It’s to understand the reasoning behind each concept well enough that you could explain it to a colleague on a whiteboard.
A few things I noticed while putting this together: the questions that interviewers love most aren’t the hardest ones — they’re the ones that reveal how you think about types. Can you explain why unknown is safer than any? Do you know when to reach for a discriminated union vs. a type guard? Can you articulate the trade-off between interface and type? These conversations matter more than reciting the syntax for variadic tuple types.
If you’re preparing for an interview, I’d suggest picking five questions from each tier that you’re least confident about and writing your own code examples from scratch. If you can produce a working snippet without looking at the answer, you know the concept — not just the words.
Good luck out there.