Module 34: Interview Preparation
Master TypeScript interview questions, coding challenges, and common patterns asked by top companies.
1. Type System Questions
Q1: What is the difference between type and interface?
// Interface - can be extended and merged
interface User {
name: string;
}
interface User { // Declaration merging
age: number;
}
interface Admin extends User { // Can extend
role: string;
}
// Type - more flexible, can't be merged
type User = {
name: string;
};
type Admin = User & { // Must use intersection
role: string;
};
// Type can represent unions, primitives, tuples
type ID = string | number;
type Point = [number, number];
Answer: Interfaces can be extended and support declaration merging. Types are more flexible and can represent unions, primitives, and complex types.
Q2: Explain unknown vs any
// any - disables type checking
let value: any = "hello";
value.toUpperCase(); // OK
value.nonExistent(); // OK (no error)
// unknown - requires type checking
let value2: unknown = "hello";
value2.toUpperCase(); // Error
if (typeof value2 === "string") {
value2.toUpperCase(); // OK
}
Answer: unknown is type-safe, requiring type guards before use. any disables type checking completely.
Q3: What are conditional types?
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
// Practical example
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null>; // string
2. Advanced Type Challenges
Challenge 1: Deep Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
interface Data {
user: {
name: string;
address: {
street: string;
};
};
}
type ReadonlyData = DeepReadonly<Data>;
// All nested properties are readonly
Challenge 2: Pick By Type
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface User {
id: number;
name: string;
age: number;
email: string;
}
type StringProps = PickByType<User, string>;
// { name: string; email: string; }
Challenge 3: Required Keys
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
interface User {
name: string;
age?: number;
email: string;
}
type Required = RequiredKeys<User>; // "name" | "email"
Challenge 4: Function Property Names
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
interface User {
name: string;
getName(): string;
age: number;
setAge(age: number): void;
}
type Methods = FunctionPropertyNames<User>;
// "getName" | "setAge"
3. Practical Coding Problems
Problem 1: Type-Safe Event Emitter
type EventMap = {
"user:created": { id: string; name: string };
"user:deleted": { id: string };
"post:published": { postId: string; authorId: string };
};
class TypedEventEmitter<T extends Record<string, any>> {
private listeners = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(
event: K,
callback: (data: T[K]) => void
): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
}
emit<K extends keyof T>(event: K, data: T[K]): void {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(cb => cb(data));
}
}
}
const emitter = new TypedEventEmitter<EventMap>();
emitter.on("user:created", (data) => {
// data is typed as { id: string; name: string }
console.log(data.name);
});
emitter.emit("user:created", { id: "1", name: "John" }); // OK
emitter.emit("user:created", { id: "1" }); // Error: missing name
Problem 2: Type-Safe Builder Pattern
class QueryBuilder<T extends Record<string, any>> {
private filters: Partial<T> = {};
private sortField?: keyof T;
private limitValue?: number;
where<K extends keyof T>(field: K, value: T[K]): this {
this.filters[field] = value;
return this;
}
sort(field: keyof T): this {
this.sortField = field;
return this;
}
limit(value: number): this {
this.limitValue = value;
return this;
}
build(): QueryParams<T> {
return {
filters: this.filters,
sort: this.sortField,
limit: this.limitValue
};
}
}
interface User {
id: string;
name: string;
age: number;
}
const query = new QueryBuilder<User>()
.where("name", "John") // Type-safe
.where("age", 25) // Type-safe
.sort("name") // Type-safe
.limit(10)
.build();
Problem 3: Type-Safe State Machine
type State = "idle" | "loading" | "success" | "error";
type Transitions = {
idle: "loading";
loading: "success" | "error";
success: "idle";
error: "idle";
};
class StateMachine<S extends string, T extends Record<S, string>> {
constructor(private state: S) {}
getState(): S {
return this.state;
}
transition<From extends S, To extends T[From]>(
from: From,
to: To extends S ? To : never
): void {
if (this.state !== from) {
throw new Error(`Invalid transition from ${this.state}`);
}
this.state = to as S;
}
}
const machine = new StateMachine<State, Transitions>("idle");
machine.transition("idle", "loading"); // OK
machine.transition("loading", "success"); // OK
machine.transition("idle", "success"); // Error: invalid transition
4. Common Interview Questions
Q4: Difference between never and void?
// void - function returns undefined
function log(message: string): void {
console.log(message);
// implicitly returns undefined
}
// never - function never returns
function throwError(message: string): never {
throw new Error(message);
// execution never reaches end
}
function infinite(): never {
while (true) {}
}
Q5: What is type narrowing?
function process(value: string | number) {
if (typeof value === "string") {
// TypeScript narrows type to string
console.log(value.toUpperCase());
} else {
// TypeScript narrows type to number
console.log(value.toFixed(2));
}
}
Q6: Explain generic constraints
// Without constraint
function getProperty<T, K>(obj: T, key: K) {
return obj[key]; // Error: K might not be in T
}
// With constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]; // OK: K is guaranteed to be in T
}
const user = { name: "John", age: 25 };
getProperty(user, "name"); // OK
getProperty(user, "invalid"); // Error
Q7: What is mapped type?
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }
type PartialUser = Optional<User>;
// { name?: string; age?: number; }
Q8: Difference between Partial and Required?
interface User {
name: string;
age?: number;
email?: string;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string; }
type RequiredUser = Required<User>;
// { name: string; age: number; email: string; }
5. Performance Questions
Q9: How to optimize TypeScript compilation?
{
"compilerOptions": {
"incremental": true, // Enable incremental compilation
"skipLibCheck": true, // Skip type checking .d.ts files
"skipDefaultLibCheck": true // Skip default lib checking
},
"exclude": ["node_modules", "dist"]
}
Q10: What are declaration files?
// math.d.ts - type declarations only
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
// Usage
import { add } from "./math";
const result = add(2, 3); // Type-safe without implementation
6. Behavioral Questions
Q11: When would you use any vs unknown?
- Use
unknownwhen the type is truly unknown and will be validated - Avoid
anyunless absolutely necessary for gradual migration
Q12: How do you handle type-safety with third-party libraries?
- Install
@typespackages - Create custom
.d.tsfiles - Use module augmentation
Q13: What's your approach to error handling in TypeScript?
- Use custom error classes
- Implement Result/Either types
- Type-safe error boundaries
Key Takeaways
✅ Type vs Interface - know when to use each
✅ Unknown vs Any - prefer unknown for safety
✅ Conditional types for advanced patterns
✅ Type guards for narrowing
✅ Generic constraints for type safety
✅ Practice type challenges regularly
Practice Resources
Practice Exercises
Exercise 1: Implement DeepPartial
Create a type that makes all nested properties optional.
Exercise 2: Type-Safe API Client
Build a type-safe REST API client with proper typing.
Exercise 3: Advanced Utility Types
Implement custom utility types like DeepReadonly, PickByType, RequiredKeys.
Next Steps
In Module 35, we'll explore TypeScript Future and Best Practices to complete your TypeScript mastery journey.