📘
Intermediate
13 min readTypeScript Interview Questions
TypeScript types, interfaces, generics, and advanced concepts
Essential TypeScript Interview Questions
Master TypeScript concepts commonly asked in frontend interviews. This guide covers types, interfaces, generics, utility types, and advanced TypeScript features.
1. Basic Types in TypeScript
// Primitive types
let name: string = 'John';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let undef: undefined = undefined;
// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array = ['a', 'b', 'c'];
// Tuple - fixed length and types
let tuple: [string, number] = ['John', 30];
let rgb: [number, number, number] = [255, 0, 0];
// Enum
enum Role {
Admin = 'ADMIN',
User = 'USER',
Guest = 'GUEST'
}
let userRole: Role = Role.Admin;
// Any - avoid if possible
let anything: any = 'hello';
anything = 42; // No type checking
// Unknown - safer than any
let value: unknown = 'hello';
if (typeof value === 'string') {
console.log(value.toUpperCase()); // Type guard required
}
// Void - no return value
function log(message: string): void {
console.log(message);
}
// Never - never returns
function throwError(message: string): never {
throw new Error(message);
}
2. Interface vs Type
// Interface - for object shapes
interface User {
id: number;
name: string;
email?: string; // Optional
readonly createdAt: Date; // Read-only
}
// Interface extension
interface Admin extends User {
role: 'admin';
permissions: string[];
}
// Interface merging (declaration merging)
interface User {
age: number; // Adds to User interface
}
// Type alias - more flexible
type ID = string | number;
type Point = {
x: number;
y: number;
};
// Type can use unions
type Status = 'pending' | 'approved' | 'rejected';
// Type can use intersections
type Employee = User & {
department: string;
salary: number;
};
// When to use what:
// ✅ Interface: Object shapes, classes, extending
// ✅ Type: Unions, intersections, primitives, computed properties
3. Generics
// Generic function
function identity(value: T): T {
return value;
}
const num = identity(42);
const str = identity('hello');
const auto = identity(true); // Type inference
// Generic with constraints
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // string
const age = getProperty(user, 'age'); // number
// getProperty(user, 'invalid'); // Error
// Generic interface
interface ApiResponse {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse;
type PostsResponse = ApiResponse;
// Generic class
class DataStore {
private data: T[] = [];
add(item: T): void {
this.data.push(item);
}
get(index: number): T {
return this.data[index];
}
getAll(): T[] {
return this.data;
}
}
const userStore = new DataStore();
userStore.add({ id: 1, name: 'John', createdAt: new Date() });
// Multiple generic types
function merge(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge({ name: 'John' }, { age: 30 });
// merged has both name and age properties
4. Utility Types
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial - all properties optional
type PartialUser = Partial;
// { id?: number; name?: string; email?: string; age?: number; }
function updateUser(user: User, updates: Partial): User {
return { ...user, ...updates };
}
// Required - all properties required
type RequiredUser = Required;
// Readonly - all properties read-only
type ReadonlyUser = Readonly;
const user: ReadonlyUser = { id: 1, name: 'John', email: 'john@example.com', age: 30 };
// user.name = 'Jane'; // Error: Cannot assign to 'name'
// Pick - select specific properties
type UserPreview = Pick;
// { id: number; name: string; }
// Omit - exclude specific properties
type UserWithoutEmail = Omit;
// { id: number; name: string; age: number; }
// Record - create object type with specific keys and value type
type Roles = 'admin' | 'user' | 'guest';
type RolePermissions = Record;
const permissions: RolePermissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
// Exclude - exclude from union
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T2 = Exclude; // string | number
// Extract - extract from union
type T3 = Extract<'a' | 'b' | 'c', 'a' | 'c'>; // 'a' | 'c'
// NonNullable - exclude null and undefined
type T4 = NonNullable; // string | number
// ReturnType - get function return type
function getUser() {
return { id: 1, name: 'John' };
}
type UserReturnType = ReturnType;
// { id: number; name: string; }
// Parameters - get function parameter types
function createUser(name: string, age: number) {}
type CreateUserParams = Parameters;
// [string, number]
5. Type Guards and Narrowing
// typeof guard
function process(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase(); // TypeScript knows it's string
} else {
return value.toFixed(2); // TypeScript knows it's number
}
}
// instanceof guard
class Dog {
bark() { console.log('Woof!'); }
}
class Cat {
meow() { console.log('Meow!'); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// in operator
interface Admin {
role: 'admin';
permissions: string[];
}
interface User {
role: 'user';
email: string;
}
function hasPermissions(user: Admin | User): boolean {
if ('permissions' in user) {
return user.permissions.length > 0;
}
return false;
}
// Custom type guard
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // TypeScript knows it's string
}
}
// Discriminated unions
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
default:
// Exhaustiveness check
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
}
6. Advanced Types
// Mapped types
type Readonly = {
readonly [P in keyof T]: T[P];
};
type Optional = {
[P in keyof T]?: T[P];
};
// Conditional types
type IsString = T extends string ? 'yes' : 'no';
type A = IsString; // 'yes'
type B = IsString; // 'no'
// infer keyword
type GetReturnType = T extends (...args: any[]) => infer R ? R : never;
type Return = GetReturnType<() => number>; // number
// Template literal types
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize}`;
// 'onClick' | 'onFocus' | 'onBlur'
type PropGetter = `get${Capitalize}`;
type UserGetter = PropGetter<'name' | 'age'>;
// 'getName' | 'getAge'
// Index signature
interface StringMap {
[key: string]: string;
}
const map: StringMap = {
name: 'John',
email: 'john@example.com'
};
// keyof operator
interface User {
name: string;
age: number;
}
type UserKeys = keyof User; // 'name' | 'age'
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
// typeof operator
const config = {
api: 'https://api.example.com',
timeout: 5000
};
type Config = typeof config;
// { api: string; timeout: number; }
7. TypeScript with React
import { FC, ReactNode, useState, useEffect } from 'react';
// Function component with props
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
const Button: FC = ({ label, onClick, variant = 'primary', disabled = false }) => {
return (
);
};
// Props with children
interface CardProps {
title: string;
children: ReactNode;
}
const Card: FC = ({ title, children }) => {
return (
{title}
{children}
);
};
// useState with type
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
// useEffect with cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => clearInterval(timer);
}, []);
// Event handlers
interface FormProps {
onSubmit: (data: { name: string; email: string }) => void;
}
const Form: FC = ({ onSubmit }) => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// ...
};
const handleChange = (e: React.ChangeEvent) => {
console.log(e.target.value);
};
const handleClick = (e: React.MouseEvent) => {
console.log('Clicked');
};
return (
);
};
// Custom hooks with TypeScript
function useFetch(url: string) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
// Usage
const { data: user } = useFetch('/api/user');
8. Common TypeScript Errors and Fixes
// Error: Object is possibly 'null'
const user: User | null = getUser();
// user.name; // Error
// Fix 1: Optional chaining
user?.name;
// Fix 2: Nullish coalescing
const name = user?.name ?? 'Guest';
// Fix 3: Type guard
if (user !== null) {
console.log(user.name); // OK
}
// Error: Type 'string | undefined' is not assignable to type 'string'
const obj: { name?: string } = {};
// const name: string = obj.name; // Error
// Fix: Non-null assertion (use carefully!)
const name: string = obj.name!;
// Fix: Type guard
if (obj.name) {
const name: string = obj.name; // OK
}
// Error: Argument of type 'X' is not assignable to parameter of type 'Y'
interface User {
id: number;
name: string;
}
function updateUser(user: User) {}
const partialUser = { name: 'John' };
// updateUser(partialUser); // Error
// Fix: Type assertion
updateUser(partialUser as User);
// Fix: Satisfy full type
const fullUser: User = { id: 1, name: 'John' };
updateUser(fullUser);
Key Interview Takeaways:
- Use
interfacefor object shapes,typefor unions/intersections - Generics make reusable, type-safe components
- Utility types (Partial, Pick, Omit, etc.) for type transformations
- Type guards narrow types safely
- Use
unknowninstead ofanywhen possible - Discriminated unions with
kindortypeproperty - React + TypeScript: Type props, state, and events
keyofandtypeoffor working with existing types