WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

TypeScript · Guru · question 82 of 100

How can you leverage TypeScript’s type system to enforce specific architectural constraints?

📕 Buy this interview preparation book: 100 TypeScript questions & answers — PDF + EPUB for $5

Leveraging TypeScript’s type system to enforce specific architectural constraints can provide several benefits, such as ensuring code consistency, improving maintainability, and reducing the likelihood of defects. To achieve this, you can use the following concepts and features:

1. **Type Aliases**: Create custom types for architectural components, making it easier to understand their purpose and enforce specific constraints.

type UserID = string;
type UserName = string;

// Use custom types
function getUser(id: UserID): UserName {
    // ...
}

2. **Interfaces**: Define the structure of complex objects or components, ensuring they adhere to specific architectural patterns.

// Interface for a typical data repository
interface Repository<T> {
    getById(id: number): T | null;
    getAll(): Array<T>;
    save(entity: T): void;
}

3. **Abstract Classes**: Enforce specific contracts or behavior by using abstract classes as base classes for architectural components.

abstract class BaseController<T> {
    protected abstract repository: Repository<T>;

    getById(id: number): T | null {
        return this.repository.getById(id);
    }

    getAll(): Array<T> {
        return this.repository.getAll();
    }
}

4. **Namespaces**: Group architectural components or utility functions into separate namespaces, providing better organization and avoiding name conflicts.

namespace Validation {
    export interface Validator<T> {
        isValid(value: T): boolean;
    }

    export class EmailValidator implements Validator<string> {
        isValid(email: string): boolean {
            // Perform email validation logic
        }
    }
}

5. **Mapped Types**: Create new types by mapping through existing ones, changing properties or modifiers in the process.

type ReadOnly<T> = { readonly [K in keyof T]: T[K] };

// Example Usage
interface User {
    id: number;
    name: string;
}

type ReadOnlyUser = ReadOnly<User>;

6. **Conditional Types**: Apply specific constraints based on the input type, showing their utility in complex architectures.

type JSONValue =
    | string
    | number
    | boolean
    | null
    | JSONArray
    | JSONObject;
type JSONified<T> = {
    [K in keyof T]: T[K] extends JSONValue
        ? T[K]
        : T[K] extends Array<any>
        ? JSONifiedArray<T[K]>
        : JSONifiedObject<T[K]>
};

type JSONArray = Array<JSONified<any>>;
type JSONObject = JSONifiedObject<any>;
interface JSONifiedObject<T> {
    [P in keyof T]: (T[P] extends object) ? JSONified<T[P]> : T[P];
}

In summary, leveraging TypeScript’s powerful type system can help you enforce and maintain specific architectural constraints throughout your codebase. By using a combination of type aliases, interfaces, abstract classes, namespaces, mapped types, and conditional types, you can create code that adheres to the desired architectural patterns, ensuring consistency and maintainability.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic TypeScript interview — then scores it.
📞 Practice TypeScript — free 15 min
📕 Buy this interview preparation book: 100 TypeScript questions & answers — PDF + EPUB for $5

All 100 TypeScript questions · All topics