Как пройти собеседование по TypeScript: топ вопросов от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему TypeScript — must have для современного разработчика

TypeScript прочно занял нишу стандарта де-факто в enterprise-разработке. По данным опросов Stack Overflow, более 70% frontend-разработчиков используют или планируют использовать TypeScript. На собеседованиях без вопросов по этой технологии не обходится — даже если вакансия формально указана как "JavaScript Developer".

В этой статье мы разберём ключевые темы, которые вам обязательно зададут на техническом интервью: от базовых типов до сложных дженериков и условных типов. Материал построен по принципу "от простого к сложному", чтобы вы могли системно подготовиться.

1. База: типы, интерфейсы и их отличия

1.1. Примитивные и специальные типы

Начнём с основ. Интервьюер почти наверняка спросит про any, unknown, never и void.

  • any — отключает проверку типов. Используется только как крайняя мера при миграции.
  • unknown — безопасная альтернатива any. Требует проверки типа перед использованием.
  • never — тип, который никогда не происходит (функции, выбрасывающие исключения, или бесконечные циклы).
  • void — отсутствие возвращаемого значения у функции.
function throwError(msg: string): never {
    throw new Error(msg);
}

function log(msg: string): void {
    console.log(msg);
}

let data: unknown = JSON.parse('{"name":"John"}');
// data.name // Ошибка! Нужна проверка
if (typeof data === 'object' && data !== null) {
    console.log((data as Record<string, unknown>).name);
}

1.2. Interface vs Type: что выбрать?

Это классический вопрос. Оба подхода позволяют описывать структуру объекта, но есть нюансы:

  • Interface — расширяем (extends), может быть объединён (declaration merging).
  • Type — может описывать union-типы, intersection, примитивы. Не подлежит declaration merging.
// Interface - расширение
interface User {
    name: string;
}
interface User {
    age: number; // declaration merging
}
const user: User = { name: 'Alice', age: 30 };

// Type - union
type Status = 'active' | 'inactive';
type UserOrStatus = User | Status;

Совет: Для объектов и классов используйте interface, для сложных композиций — type.

2. Дженерики и утилитарные типы

2.1. Дженерики: обобщённое программирование

Без дженериков не обходится ни одно серьёзное интервью. Умение написать тип, который адаптируется под переданный аргумент, — показатель зрелости разработчика.

// Простой дженерик
function identity<T>(arg: T): T {
    return arg;
}

// Ограничение дженерика
function getLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

// Дженерик для массива
function last<T>(arr: T[]): T | undefined {
    return arr[arr.length - 1];
}

Часто спрашивают про infer — ключевое слово для вывода типов внутри условных конструкций:

// Извлекаем тип элемента из Promise
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type Result = Unwrap<Promise<number>>; // number

2.2. Утилитарные типы (Utility Types)

Знание встроенных утилит — обязательный минимум. Интервьюер может попросить реализовать аналог вручную.

  • Partial<T> — все поля становятся опциональными.
  • Required<T> — все поля обязательные.
  • Pick<T, K> — выбирает только указанные ключи.
  • Omit<T, K> — исключает указанные ключи.
  • Record<K, V> — создаёт тип с ключами K и значениями V.
interface User {
    id: number;
    name: string;
    email: string;
}

// Partial - все поля необязательны
type PartialUser = Partial<User>;

// Pick - только id и name
type UserPreview = Pick<User, 'id' | 'name'>;

// Record
type Role = 'admin' | 'user';
type RolePermissions = Record<Role, string[]>;

Вопрос с подвохом: "Как сделать глубокий Partial (DeepPartial)?" — это проверка на понимание рекурсивных типов.

type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

3. Advanced-темы: условные типы и mapped types

3.1. Условные типы (Conditional Types)

Позволяют создавать типы, которые зависят от условий. Это мощный инструмент для библиотек и фреймворков.

// Базовый условный тип
type IsString<T> = T extends string ? 'yes' : 'no';
type Test1 = IsString<'hello'>; // 'yes'
type Test2 = IsString<number>;  // 'no'

// Распределительные условные типы (Distributive Conditional Types)
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>; // string[] | number[]

На собеседовании могут попросить объяснить, почему string | number превращается в string[] | number[], а не в (string | number)[]. Ответ: TypeScript автоматически дистрибутивно применяет условный тип к каждому члену union.

3.2. Mapped Types (отображаемые типы)

Позволяют трансформировать существующие типы, перебирая их ключи. Классический пример — сделать все поля readonly:

type MyReadonly<T> = {
    readonly [P in keyof T]: T[P];
};                

Блоги

Рекомендаций по книгам