Menu

Вопросы на собеседовании по TypeScript: 25 ответов с кодом

25 вопросов по TypeScript для собеседования с короткими правильными ответами и кодом, от начального уровня до продвинутого: any и unknown, interface и type, дженерики, сужение типов, служебные типы, отображённые и условные типы, структурная типизация, tsconfig и TypeScript 7.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Здесь собраны вопросы по TypeScript, которые на собеседованиях задают чаще всего, с коротким ответом, который вы должны уметь дать, и небольшим примером. Они сгруппированы по уровню: на позиции junior обычно хватает первой группы, а от senior ждут, что типы из последней группы он напишет по памяти.

Вопросы начального уровня

1. Что такое TypeScript?

TypeScript это JavaScript со статическими типами, созданный Microsoft. Вы аннотируете значения типами, компилятор их проверяет, затем удаляет типы и выдаёт обычный JavaScript, который работает везде, где работает JavaScript.

Без комментария @ts-expect-error второй вызов даёт ошибку компиляции, так что баг никогда не попадёт в продакшен.

2. Проверяет ли TypeScript типы во время выполнения?

Нет. Типы стираются при компиляции; в результате от них не остаётся и следа. Пример выше печатает 23, потому что во время выполнения никто не знает, что a должно быть числом. Чтобы проверять данные извне программы (JSON, пользовательский ввод, ответы API), пишут настоящие проверки, например защитник типа или валидатор схемы.

3. Какие есть базовые типы?

string, number (один тип для целых и дробных; int нет), boolean, bigint, symbol, null и undefined, а также массивы (number[]), кортежи ([string, number]), объектные типы, any, unknown, never и void. Пишите имена строчными буквами: String и Number это типы объектов-обёрток.

4. Чем any отличается от unknown?

Оба принимают любое значение. any к тому же отключает проверку, так что любая операция компилируется. unknown не разрешает ничего, пока вы его не сузите, поэтому это безопасный тип для ещё не проверенных значений.

5. Что такое вывод типов?

Компилятор сам определяет типы, которые вы не написали: let count = 0 имеет тип number, const mode = "dark" имеет литеральный тип "dark", а возвращаемый тип функции берётся из её операторов return. Обычное правило: аннотировать параметры функций и публичные возвращаемые типы, а локальные переменные оставить выводу.

6. Чем interface отличается от type?

Оба описывают формы объектов, и класс может реализовать через implements любой из них. interface можно открыть заново и слить (слияние объявлений), и он расширяет другие интерфейсы через extends. Псевдоним type может называть что угодно: объединения, кортежи, примитивы, отображённые и условные типы.

interface User { name: string }
interface User { age: number }       // merged: User has name and age

type Id = string | number;           // only a type alias can be a union
type Pair = [string, number];        // or a tuple
type Admin = User & { role: "admin" };

Распространённое соглашение: interface для форм объектов, type для всего остального. Полное сравнение на странице interface и type.

7. Что такое объединения и пересечения типов?

Объединение A | B это значение одного из типов; пока вы его не сузите, можно использовать только члены, общие для всех. Пересечение A & B это значение, которое одновременно относится к обоим типам и имеет все члены каждого.

type Id = string | number;                 // either
type Timestamped = { createdAt: Date };
type Post = { title: string } & Timestamped; // both: title and createdAt

8. Чем void отличается от never?

void это возвращаемый тип функции, которая завершается нормально, но не возвращает полезного значения. never это тип того, что не может произойти: функции, которая всегда бросает исключение или зацикливается навсегда, или объединения, из которого убраны все случаи. never можно присвоить любому типу, а типу never нельзя присвоить никакое значение.

function log(msg: string): void { console.log(msg); }
function fail(msg: string): never { throw new Error(msg); }
type Impossible = string & number; // never

Вопросы среднего уровня

9. Что такое сужение типов?

Сужение это когда компилятор следит за вашими проверками и уточняет тип внутри каждой ветки. Он понимает typeof, instanceof, in, проверки равенства, истинность и пользовательские защитники типов.

10. Что такое пользовательский защитник типа?

Функция с возвращаемым типом value is T. Когда она возвращает true, переменная вызывающего кода сужается до T. Компилятор доверяет функции, поэтому её тело должно действительно проверять значение.

interface Cat { meow(): void }

function isCat(value: unknown): value is Cat {
    return (
        typeof value === "object" &&
        value !== null &&
        "meow" in value &&
        typeof value.meow === "function"
    );
}

Функция утверждения, function assertCat(v: unknown): asserts v is Cat, сужает тип, бросая исключение вместо возврата false.

11. Что такое дженерики?

Параметры типа, которые позволяют одной функции, классу или типу работать со многими типами, сохраняя связь между входом и выходом. T выводится из аргументов, а extends ограничивает, чем он может быть.

pluck(users, "email") дал бы ошибку компиляции, потому что "email" не входит в keyof типа пользователя.

12. Что делают keyof и typeof в типе?

keyof T это объединение имён свойств T. В позиции типа typeof x даёт тип переменной. Вместе keyof typeof obj превращают ключи объекта в объединение.

const colors = { red: "#f00", green: "#0f0" };
type Colors = typeof colors;        // { red: string; green: string }
type ColorName = keyof typeof colors; // "red" | "green"

13. Что такое служебные типы? Назовите несколько.

Встроенные обобщённые типы, которые преобразуют другие типы. Чаще всего спрашивают о таких:

Служебный типРезультат
Partial<T>все свойства опциональны
Required<T>все свойства обязательны
Readonly<T>все свойства readonly
Pick<T, "a" | "b">только перечисленные свойства
Omit<T, "a">все свойства, кроме перечисленных
Record<K, V>объект с ключами K и значениями V
Exclude<U, X>, Extract<U, X>убрать или оставить члены объединения
NonNullable<T>T без null и undefined
ReturnType<F>, Parameters<F>возвращаемый тип или типы параметров функции
Awaited<T>значение, в которое разрешается промис

Полный список на странице служебные типы.

14. Что такое размеченное объединение?

Объединение объектных типов с общим литеральным свойством-«меткой». Проверка метки сужает значение до одного члена, а проверка через never в ветке default заставляет компилятор сообщать о любом забытом случае.

15. Чем as отличается от satisfies?

value as T это утверждение типа: оно велит компилятору считать значение типом T и пропускает большую часть проверок. value satisfies T проверяет значение по T, но сохраняет собственный, более точный тип значения.

type Theme = { primary: string; secondary: string };

const a = { primary: "#07f" } as Theme;        // compiles: the missing key is not reported
const b = { primary: "#07f" } satisfies Theme; // error TS2741: Property 'secondary' is missing
const c = {} as { name: string };              // compiles; c.name is undefined at run time

Когда объект полный, satisfies к тому же сохраняет его собственный выведенный тип: при проверке по Record<string, string> переменная по-прежнему точно знает свои ключи, тогда как аннотация расширила бы тип до любого строкового ключа.

16. Что делает оператор ! после переменной?

Это non-null утверждение: el! убирает null и undefined из типа. Оно не создаёт никакой проверки, поэтому, если значение на самом деле null, программа падает там, где оно используется. Предпочитайте ?., ?? или явный if.

17. Чем private отличается от #private?

private обеспечивается только компилятором; в JavaScript на выходе это обычное свойство. #field это приватное поле JavaScript, которое обеспечивает среда выполнения.

protected работает как private, но также разрешает доступ из подклассов, а readonly запрещает переприсваивание после создания объекта.

18. Чем абстрактный класс отличается от интерфейса?

Интерфейс это только тип: он описывает форму и исчезает из результата. Абстрактный класс это настоящий класс, экземпляр которого нельзя создать; в нём могут быть реализованные методы, поля и конструкторы рядом с abstract-членами, которые обязаны реализовать подклассы. Класс может реализовать много интерфейсов, но расширить только один класс.

abstract class Repository<T> {
    protected items: T[] = [];
    abstract validate(item: T): boolean; // subclasses must implement
    add(item: T): void {                 // shared implementation
        if (this.validate(item)) this.items.push(item);
    }
}

19. Что такое перегрузка функций в TypeScript?

Несколько сигнатур вызова, за которыми следует одна реализация с сигнатурой, совместимой со всеми ними. Вызывающий код видит только перегрузки, поэтому каждый вызов получает точный возвращаемый тип.

function parse(value: string): number;
function parse(value: string[]): number[];
function parse(value: string | string[]): number | number[] {
    return Array.isArray(value) ? value.map(Number) : Number(value);
}

const one = parse("4");          // number
const many = parse(["1", "2"]);  // number[]

Если возвращаемый тип не зависит от типа аргумента, проще одна сигнатура с параметром-объединением.

Вопросы продвинутого уровня

20. Что такое структурная типизация?

TypeScript сравнивает типы по форме, а не по имени. Принимается любое значение с нужными свойствами, даже если оно никогда не объявлялось с этим типом.

Лишнее color здесь не мешает, потому что pixel это переменная. Передача объектного литерала напрямую, show({ x: 3, y: 4, color: "red" }), даёт ошибку лишнего свойства, а эта проверка применяется только к свежим литералам. Когда два типа с одинаковой формой не должны смешиваться (UserId и OrderId, оба строки), используйте брендированный тип: type UserId = string & { readonly __brand: "UserId" }.

21. Как самому реализовать Readonly и Partial?

Через отображённые типы: перебрать keyof T и добавить модификатор к каждому свойству.

type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
type MyPartial<T> = { [K in keyof T]?: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // "-" removes a modifier

type User = { name: string; age: number };
type Draft = MyPartial<User>; // { name?: string; age?: number }

22. Что такое условные типы и infer?

T extends U ? X : Y выбирает тип по условию. Внутри условия infer объявляет переменную типа, которая захватывает часть сопоставленного типа. Условные типы распределяются по объединениям.

type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type ElementOf<T> = T extends (infer E)[] ? E : T;

type A = MyReturnType<() => Promise<number>>; // Promise<number>
type B = ElementOf<string[]>;                 // string
type C = ElementOf<number | boolean[]>;       // number | boolean (distributed)

Другие приёмы на странице условные типы.

23. Что такое файл .d.ts и что делает declare?

Файл объявлений содержит только типы для кода, который существует где-то ещё, например для библиотеки на JavaScript или API браузера. declare утверждает, что значение существует, не создавая его: declare const VERSION: string; компилируется в ничто. Типы библиотек поставляются вместе с пакетом или берутся из пакетов @types/{name}, а для вашего собственного кода tsc генерирует файлы .d.ts при declaration: true.

24. Что делает strict и какие опции tsconfig важнее всего?

"strict": true включает noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables и strictBuiltinIteratorReturn. В TypeScript 7 он включён по умолчанию. Другие опции, о которых спрашивают на собеседованиях:

ОпцияОтвет в одну строку
targetкакую версию JavaScript использует результат
moduleформат модулей на выходе: nodenext следует правилам Node, esnext/preserve сохраняют import для бандлера
moduleResolutionкак находятся импорты: nodenext требует ./file.js в файлах ES-модулей, bundler принимает ./file
noUncheckedIndexedAccessarr[i] включает undefined в свой тип
pathsпсевдонимы импорта только для проверки типов; в результате псевдоним остаётся, поэтому его должен разрешить бандлер или поле imports для Node
skipLibCheckне проверять файлы .d.ts ради скорости
verbatimModuleSyntaxимпорты, используемые только как типы, нужно помечать type; остальные импорты сохраняются как написаны

25. Что такое TypeScript 7?

Компилятор TypeScript, переписанный на Go как нативная программа и выпущенный как версия 7 npm-пакета typescript. Команда по-прежнему tsc, язык тот же, но работает он гораздо быстрее компилятора 6.x на JavaScript: команда TypeScript сообщает об ускорении от 8 до 12 раз на типичных полных сборках благодаря нативному коду и многопоточности с общей памятью. Кроме того, он окончательно убирает то, что в 6.0 было объявлено устаревшим; например, baseUrl, outFile, moduleResolution: "node10", module: "amd" и alwaysStrict: false теперь дают ошибки (TS5102, TS5108) с требованием их удалить.

Часто задаваемые вопросы

Какие вопросы по TypeScript чаще всего задают на собеседовании?

Чаще всего спрашивают: разницу между any и unknown, interface и type, как работают дженерики, как работают сужение и защитники типов, что делают служебные типы вроде Partial, Pick и Omit и проверяет ли TypeScript типы во время выполнения (нет).

Какие вопросы по TypeScript задают senior-разработчикам?

Будьте готовы писать типы, а не только читать их: реализовать Readonly или ReturnType через отображённые и условные типы, объяснить структурную типизацию и когда нужны брендированные типы, спроектировать размеченное объединение с проверкой полноты и обсудить выбор настроек tsconfig, например strict, noUncheckedIndexedAccess и разрешение модулей.

Как подготовиться к собеседованию по TypeScript?

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

Спрашивают ли TypeScript на собеседованиях по React и Angular?

Обычно да. Сам Angular написан на TypeScript, и приложения на Angular пишут на нём же, поэтому на собеседованиях по Angular его, как правило, знание подразумевается, включая декораторы вроде @Component и модификаторы доступа. На собеседованиях по React для кодовой базы на TypeScript часто просят типизировать props, состояние и обработчики событий, а это те же интерфейсы, объединения и дженерики, что показаны здесь.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ