Generic Functions
Generic Functions
Generics allow you to write reusable code that works with multiple types while maintaining type safety.
Basic Generic Function
// Without generics - loses type info
function identity(value: any): any {
return value;
}
// With generics - preserves type
function identity<T>(value: T): T {
return value;
}
// Usage
const num = identity<number>(42); // type: number
const str = identity<string>("hello"); // type: string
// Type inference
const inferred = identity(42); // TypeScript infers number
Multiple Type Parameters
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const result = pair("hello", 42); // [string, number]
Generic Arrow Functions
const getFirst = <T>(arr: T[]): T | undefined => arr[0];
const first = getFirst([1, 2, 3]); // number | undefined
const firstStr = getFirst(["a", "b"]); // string | undefined
Generic with Default
function createArray<T = string>(length: number, fill: T): T[] {
return Array(length).fill(fill);
}
const strings = createArray(3, "hello"); // string[]
const numbers = createArray<number>(3, 42); // number[]
Generic Classes
Generic Classes
Generic classes allow you to create classes that work with different types.
Basic Generic Class
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
const numContainer = new Container<number>(42);
const strContainer = new Container<string>("hello");
Generic Stack
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
Generic Interface
interface Repository<T> {
findById(id: number): T | undefined;
findAll(): T[];
create(item: T): T;
update(id: number, item: Partial<T>): T;
delete(id: number): void;
}
class UserRepository implements Repository<User> {
private users: User[] = [];
findById(id: number) {
return this.users.find(u => u.id === id);
}
findAll() {
return this.users;
}
create(user: User) {
this.users.push(user);
return user;
}
update(id, updates) {
const user = this.findById(id);
if (user) Object.assign(user, updates);
return user!;
}
delete(id) {
this.users = this.users.filter(u => u.id !== id);
}
}
Generic Constraints
Generic Constraints
Constraints limit which types can be used with generics.
Using extends
// T must have a length property
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(42); // Error! number has no length
keyof Constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // OK
getProperty(user, "age"); // OK
// getProperty(user, "email"); // Error!
Class Constraints
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
Generic Utility with Constraints
function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge(
{ name: "Alice" },
{ age: 30 }
); // { name: string; age: number }
Practice Problems
Create a reusable React component implementing Generics. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Generics using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Generics for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What does `<T>` represent in a generic function?
2. How do you constrain a generic type?
3. What does `keyof` do in a generic constraint?
4. Can generic classes have multiple type parameters?
Flashcards
Question
What is a generic type parameter?
Click to reveal answer
Answer
A placeholder type that can be replaced with any type
Question
How do you constrain generics?
Click to reveal answer
Answer
Use extends: <T extends Constraint>
Question
What is keyof?
Click to reveal answer
Answer
An operator that creates a union of property names
Question
Can generics have default types?
Click to reveal answer
Answer
Yes: <T = string>
Question
What is Generics?
Click to reveal answer
Answer
Generics is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Generics provide type safety while maintaining flexibility
- 2.Type parameters (T, U) can be replaced with any type
- 3.Use extends to constrain generic types
- 4.keyof creates a union of property names
- 5.TypeScript infers generic types when possible
Interview Tips
- •Explain why generics are better than any
- •Show examples of generic functions and classes
- •Demonstrate understanding of generic constraints
Cheat Sheet
Cheat Sheet
Generic Function
function identity<T>(value: T): T {
return value;
}
const num = identity(42);
Generic Class
class Box<T> {
value: T;
constructor(v: T) { this.value = v; }
}
Constraints
function logLength<T extends { length: number }>(arg: T) {
console.log(arg.length);
}
keyof
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}