What are generics and why are they useful? Provide an example of a generic function that demonstrates type safety.
5 minadvancedtypescriptgenericsusefulgenericfunction
Quick Answer
Generics allow you to create reusable components that work with multiple types while maintaining type safety. They act as placeholders for types that will be specified later.
Detailed Answer
What are generics and why are they useful? Provide an example of a generic function that demonstrates type safety.
Answer: Generics allow you to create reusable components that work with multiple types while maintaining type safety. They act as placeholders for types that will be specified later.
Why Generics Are Useful:
- Type Safety: Catch errors at compile time
- Code Reusability: Write once, use with multiple types
- IntelliSense: Better IDE support and autocomplete
- Flexibility: Work with any type while maintaining constraints
Basic Generic Function:
// Without generics - loses type information
function identity(arg: any): any {
return arg;
}
// With generics - preserves type information
function identity<T>(arg: T): T {
return arg;
}
const stringResult = identity<string>("hello"); // Type: string
const numberResult = identity<number>(42); // Type: number
const inferredResult = identity("world"); // Type: "world" (inferred)
Generic Constraints:
// Constraint: T must have a length property
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK - string has length
logLength([1, 2, 3]); // OK - array has length
logLength(42); // Error - number doesn't have length
Multiple Type Parameters:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const result = merge(
{ name: "John" },
{ age: 30 }
); // Type: { name: string } & { age: number }
Generic Classes:
class Container<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
get(index: number): T | undefined {
return this.items[index];
}
getAll(): T[] {
return [...this.items];
}
}
const stringContainer = new Container<string>();
stringContainer.add("hello");
stringContainer.add("world");
const numberContainer = new Container<number>();
numberContainer.add(1);
numberContainer.add(2);
Advanced Generic Patterns:
// Conditional types
type ApiResponse<T> = T extends string
? { message: T }
: { data: T };
// Mapped types with generics
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// Generic utility functions
function createAsyncHandler<T, R>(
handler: (data: T) => Promise<R>
): (data: T) => Promise<R> {
return async (data: T) => {
try {
return await handler(data);
} catch (error) {
console.error('Handler error:', error);
throw error;
}
};
}
Coding Challenge: Create a type-safe function that deep clones an object while preserving all type information.
Answer:
// Basic deep clone with type preservation
function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}
if (obj instanceof Array) {
return obj.map(item => deepClone(item)) as T;
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags) as T;
}
if (obj instanceof Map) {
const clonedMap = new Map();
for (const [key, value] of obj) {
clonedMap.set(deepClone(key), deepClone(value));
}
return clonedMap as T;
}
if (obj instanceof Set) {
const clonedSet = new Set();
for (const value of obj) {
clonedSet.add(deepClone(value));
}
return clonedSet as T;
}
// Handle plain objects
const clonedObj = {} as T;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// Advanced version with better type inference and error handling
type DeepCloneable =
| string
| number
| boolean
| null
| undefined
| Date
| RegExp
| Map<any, any>
| Set<any>
| Array<any>
| { [key: string]: any };
function advancedDeepClone<T extends DeepCloneable>(obj: T): T {
// Handle primitives and null/undefined
if (obj === null || obj === undefined || typeof obj !== 'object') {
return obj;
}
// Handle Date objects
if (obj instanceof Date) {
return new Date(obj.getTime()) as T;
}
// Handle RegExp objects
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags) as T;
}
// Handle Map objects
if (obj instanceof Map) {
const clonedMap = new Map();
for (const [key, value] of obj) {
clonedMap.set(advancedDeepClone(key), advancedDeepClone(value));
}
return clonedMap as T;
}
// Handle Set objects
if (obj instanceof Set) {
const clonedSet = new Set();
for (const value of obj) {
clonedSet.add(advancedDeepClone(value));
}
return clonedSet as T;
}
// Handle Arrays
if (Array.isArray(obj)) {
return obj.map(item => advancedDeepClone(item)) as T;
}
// Handle plain objects
const clonedObj = {} as T;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
clonedObj[key] = advancedDeepClone(obj[key]);
}
}
return clonedObj;
}
// Usage examples
interface User {
id: number;
name: string;
preferences: {
theme: 'light' | 'dark';
notifications: boolean;
};
tags: string[];
createdAt: Date;
}
const originalUser: User = {
id: 1,
name: 'John Doe',
preferences: {
theme: 'dark',
notifications: true
},
tags: ['developer', 'typescript'],
createdAt: new Date('2023-01-01')
};
const clonedUser = deepClone(originalUser);
// clonedUser has the exact same type as originalUser
// Modifying clonedUser won't affect originalUser
// Test with complex nested structures
const complexObject = {
users: new Map([
['user1', { name: 'Alice', age: 30 }],
['user2', { name: 'Bob', age: 25 }]
]),
settings: new Set(['feature1', 'feature2']),
metadata: {
version: '1.0.0',
lastUpdated: new Date(),
config: {
apiUrl: 'https://api.example.com',
timeout: 5000
}
}
};
const clonedComplex = advancedDeepClone(complexObject);
// All nested objects, Maps, Sets, and Dates are properly cloned
Key Benefits of This Implementation:
- Type Safety: Returns the exact same type as input
- Deep Cloning: Handles nested objects, arrays, Maps, Sets
- Special Object Support: Properly clones Dates, RegExp, etc.
- Performance: Efficient handling of different object types
- Error Prevention: Avoids circular reference issues with proper checks