Skip to main content

Command Palette

Search for a command to run...

TypeScript Generics: Write Flexible Code Once, Use Everywhere

Updated
5 min read
TypeScript Generics: Write Flexible Code Once, Use Everywhere
M

Turning ideas into smooth, scalable mobile experiences — one line of code at a time.

Hi, I’m Mitesh Kukdeja — a passionate React Native developer with 2+ years of hands-on experience building cross-platform apps that delight users and deliver results. From health and fitness platforms to job boards and music contest apps, I’ve helped bring a wide range of product visions to life.

What sets me apart is my obsession with clean, reusable code and user-centric UI/UX. I specialize in React Native, TypeScript, Redux Toolkit, Firebase, and REST API integration—making sure every app I build is responsive, secure, and ready for scale. I’ve also deployed apps to both the Play Store and App Store, managing the full release cycle.

My projects have included integrating real-time features like video conferencing (Agora), personalized push notifications, and advanced security implementations for enterprise clients like Godrej. Whether it’s debugging a performance bottleneck or designing a scalable component architecture, I’m all in.

My goal is to keep solving meaningful problems through technology while collaborating with creative minds. I thrive in fast-paced environments where innovation and impact matter.

If you’re building something exciting in mobile or looking for a tech partner who values quality and performance — let’s connect!

Stop writing duplicate functions - Master generics in 5 minutes

Table of Contents

What Are TypeScript Generics?

Think of generics as code templates that work with any data type while keeping everything type-safe. It's like having one delivery truck that can safely carry books, electronics, or groceries - instead of needing separate trucks for each item.

The Problem Without Generics

// Without generics - repetitive code 😫
function getFirstNumber(items: number[]): number {
    return items[0];
}

function getFirstString(items: string[]): string {
    return items[0];
}

function getFirstUser(items: User[]): User {
    return items[0];
}

Problems:

  • Code duplication
  • Hard to maintain
  • What if you need 20 different types?

Basic Generic Syntax

Generic Functions

// One function for all types! ✨
function getFirst<T>(items: T[]): T {
    return items[0];
}

// Usage
const firstNumber = getFirst([1, 2, 3]); // Type: number
const firstName = getFirst(['John', 'Jane']); // Type: string
const firstUser = getFirst(users); // Type: User

Generic Interfaces

interface ApiResponse<T> {
    data: T;
    status: number;
    message: string;
}

// Now you can use it for any data type
const userResponse: ApiResponse<User> = {
    data: { id: 1, name: "John" },
    status: 200,
    message: "Success"
};

Generic Classes

class DataStorage<T> {
    private items: T[] = [];

    add(item: T): void {
        this.items.push(item);
    }

    getAll(): T[] {
        return this.items;
    }
}

// Type-safe storage for any data
const stringStorage = new DataStorage<string>();
stringStorage.add("TypeScript"); // ✅ Works
// stringStorage.add(123); // ❌ Error!

Real-World Examples

1. Generic API Handler

async function fetchApi<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
}

// Usage
const user = await fetchApi<User>('/api/user/1');
const products = await fetchApi<Product[]>('/api/products');
// TypeScript knows the exact return types!

2. Generic Form Handler

interface FormData<T> {
    values: T;
    errors: Partial<Record<keyof T, string>>;
    isValid: boolean;
}

function useForm<T>(): FormData<T> {
    // Form logic here
    return {
        values: {} as T,
        errors: {},
        isValid: false
    };
}

// Usage
interface LoginForm {
    email: string;
    password: string;
}

const loginForm = useForm<LoginForm>();
// loginForm.values.email ✅ Type-safe!
// loginForm.values.age ❌ Error!

3. Generic Repository

interface Entity {
    id: number;
}

class Repository<T extends Entity> {
    private items: T[] = [];

    save(item: T): T {
        this.items.push(item);
        return item;
    }

    findById(id: number): T | undefined {
        return this.items.find(item => item.id === id);
    }
}

// Works with any entity
const userRepo = new Repository<User>();
const productRepo = new Repository<Product>();

Advanced Features (Quick Overview)

Generic Constraints

// T must have a length property
function logLength<T extends { length: number }>(item: T): T {
    console.log(item.length);
    return item;
}

logLength("hello"); // ✅ Works
logLength([1, 2, 3]); // ✅ Works  
// logLength(123); // ❌ Error

Multiple Type Parameters

function combine<T, U>(first: T, second: U): T & U {
    return Object.assign({}, first, second);
}

const result = combine({ name: "John" }, { age: 30 });
// result has both name and age properties

Utility Types

// Built-in utility types work great with generics
type PartialUser = Partial<User>; // All properties optional
type RequiredUser = Required<User>; // All properties required
type UserEmail = Pick<User, 'email'>; // Only email property

Best Practices

✅ Do This

// Use descriptive names for complex scenarios
interface UserRepository<TUser extends User> {
    findById(id: number): Promise<TUser>;
}

// Keep it simple for basic cases
function identity<T>(arg: T): T {
    return arg;
}

// Add constraints when needed
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

❌ Avoid This

// Too many generic parameters
function complex<A, B, C, D, E>(a: A, b: B, c: C, d: D, e: E) {}

// Using any defeats the purpose
function bad<T>(arg: any): any {
    return arg;
}

// Unnecessary generics
function simple<T>(message: string): string {
    return message; // T is not even used!
}

Common Use Cases

1. React Components

interface Props<T> {
    items: T[];
    onSelect: (item: T) => void;
}

function List<T>({ items, onSelect }: Props<T>) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index} onClick={() => onSelect(item)}>
                    {JSON.stringify(item)}
                </li>
            ))}
        </ul>
    );
}

2. API Responses

interface ApiResult<T> {
    success: boolean;
    data: T;
    error?: string;
}

// Works for any API endpoint
const userResult: ApiResult<User> = await api.getUser(1);
const productsResult: ApiResult<Product[]> = await api.getProducts();

3. Event Handling

class EventEmitter<T> {
    private listeners: ((data: T) => void)[] = [];

    on(callback: (data: T) => void): void {
        this.listeners.push(callback);
    }

    emit(data: T): void {
        this.listeners.forEach(callback => callback(data));
    }
}

const stringEmitter = new EventEmitter<string>();
stringEmitter.on((message) => console.log(message));
stringEmitter.emit("Hello!"); // Type-safe!

Conclusion

TypeScript Generics are your code multiplication tool:

Write once, use everywhere
Type safety guaranteed
Better developer experience
Easier maintenance

Quick Recap

  1. Generic Functions: function name<T>(param: T): T
  2. Generic Interfaces: interface Name<T> { data: T }
  3. Generic Classes: class Name<T> { private items: T[] }
  4. Constraints: <T extends SomeType>
  5. Multiple Types: <T, U, V>

Next Steps

  • Practice with your existing functions
  • Try the examples in your projects
  • Explore advanced utility types
  • Build something awesome! 🚀

🙏 Thank You!

Thank you for reading!

I hope you enjoyed this post. If you did, please share it with your network and stay tuned for more insights on software development. I'd love to connect with you on LinkedIn or have you follow my journey on HashNode for regular updates.

Happy Coding!
Mitesh Kukdeja