Menu

Câu hỏi phỏng vấn TypeScript có đáp án (25 câu kèm code)

25 câu hỏi phỏng vấn TypeScript với câu trả lời ngắn, chính xác và có code, chia từ cơ bản đến nâng cao: any và unknown, interface và type, generic, thu hẹp kiểu, utility type, mapped type và conditional type, structural typing, tsconfig và TypeScript 7.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Đây là những câu hỏi TypeScript mà người phỏng vấn hay hỏi nhất, kèm câu trả lời ngắn bạn nên nói được và một ví dụ nhỏ. Các câu được chia theo cấp độ; vị trí junior thường chỉ dừng ở nhóm đầu, còn vị trí senior mong bạn viết được các kiểu ở nhóm cuối mà không cần tra cứu.

Câu hỏi cơ bản

1. TypeScript là gì?

TypeScript là JavaScript có kiểu tĩnh, do Microsoft phát triển. Bạn gắn kiểu cho các giá trị, trình biên dịch kiểm tra chúng, rồi xóa kiểu đi và xuất ra JavaScript thuần chạy được ở mọi nơi JavaScript chạy.

Nếu không có chú thích @ts-expect-error, lời gọi thứ hai là lỗi biên dịch, nên bug này không bao giờ lọt ra sản phẩm.

2. TypeScript có kiểm tra kiểu lúc chạy không?

Không. Kiểu bị xóa khi biên dịch; đầu ra không còn dấu vết gì của chúng. Lần chạy ở trên in ra 23 vì lúc chạy không có gì biết rằng a phải là số. Để kiểm tra dữ liệu từ bên ngoài chương trình (JSON, dữ liệu người dùng nhập, response của API), bạn phải viết phép kiểm tra thật, như một type guard hoặc một trình xác thực schema.

3. Các kiểu cơ bản là gì?

string, number (một kiểu cho cả số nguyên và số thực; không có int), boolean, bigint, symbol, null và undefined, cộng với mảng (number[]), tuple ([string, number]), kiểu object, any, unknown, never và void. Hãy dùng tên viết thường: String và Number là kiểu object bao.

4. any và unknown khác nhau thế nào?

Cả hai đều nhận mọi giá trị. any còn tắt luôn việc kiểm tra, nên thao tác nào cũng biên dịch được. unknown không cho phép gì cho tới khi bạn thu hẹp kiểu, nên đây là kiểu an toàn cho những giá trị bạn chưa kiểm tra.

5. Suy luận kiểu (type inference) là gì?

Trình biên dịch tự tìm ra những kiểu bạn không viết: let count = 0 là number, const mode = "dark" là kiểu literal "dark", và kiểu trả về của một hàm lấy từ các câu lệnh return của nó. Quy tắc thông thường là khai báo kiểu cho tham số hàm và kiểu trả về công khai, còn biến cục bộ thì để suy luận kiểu lo.

6. interface và type khác nhau thế nào?

Cả hai đều mô tả hình dạng object, và một lớp có thể implements bất kỳ cái nào. Một interface có thể được mở lại và gộp (declaration merging) và extends các interface khác. Một type alias đặt tên được cho mọi thứ: union, tuple, kiểu nguyên thủy, mapped type và conditional 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" };

Quy ước phổ biến là interface cho hình dạng object và type cho mọi thứ còn lại. So sánh đầy đủ có ở trang interface và type.

7. Union type và intersection type là gì?

Union A | B là một giá trị thuộc một trong các kiểu; bạn chỉ dùng được những thành viên chung của tất cả cho tới khi thu hẹp kiểu. Intersection A & B là một giá trị đồng thời là cả hai, có mọi thành viên của từng kiểu.

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

8. void và never khác nhau thế nào?

void là kiểu trả về của một hàm kết thúc bình thường mà không trả về giá trị hữu ích nào. never là kiểu của thứ không thể xảy ra: một hàm luôn ném lỗi hoặc lặp vô hạn, hoặc một union đã bị loại hết mọi trường hợp. never gán được cho mọi kiểu, còn không giá trị nào gán được cho never.

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

Câu hỏi trung cấp

9. Thu hẹp kiểu (type narrowing) là gì?

Thu hẹp kiểu là việc trình biên dịch theo dõi các phép kiểm tra của bạn và làm hẹp kiểu bên trong từng nhánh. Nó hiểu typeof, instanceof, in, phép so sánh bằng, kiểm tra truthy và type guard do người dùng định nghĩa.

10. Type guard do người dùng định nghĩa là gì?

Là một hàm có kiểu trả về value is T. Khi hàm trả về true, biến ở nơi gọi được thu hẹp về T. Trình biên dịch tin hàm này, nên phần thân của nó phải thực sự kiểm tra giá trị.

interface Cat { meow(): void }

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

Một assertion function, function assertCat(v: unknown): asserts v is Cat, thu hẹp kiểu bằng cách ném lỗi thay vì trả về false.

11. Generic là gì?

Là các tham số kiểu cho phép một hàm, lớp hay kiểu làm việc với nhiều kiểu mà vẫn giữ được liên hệ giữa đầu vào và đầu ra. T được suy ra từ các đối số, và extends ràng buộc những gì nó có thể là.

pluck(users, "email") sẽ là lỗi biên dịch, vì "email" không thuộc keyof của kiểu user.

12. keyof và typeof làm gì trong một kiểu?

keyof T là union các tên thuộc tính của T. Ở vị trí kiểu, typeof x cho ra kiểu của một biến. Kết hợp lại, keyof typeof obj biến các khóa của một object thành một union.

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

13. Utility type là gì? Kể tên vài cái.

Là các kiểu generic có sẵn dùng để biến đổi kiểu khác. Những cái hay được hỏi nhất:

UtilityKết quả
Partial<T>mọi thuộc tính thành tùy chọn
Required<T>mọi thuộc tính thành bắt buộc
Readonly<T>mọi thuộc tính thành readonly
Pick<T, "a" | "b">chỉ những thuộc tính được liệt kê
Omit<T, "a">mọi thuộc tính trừ những thuộc tính được liệt kê
Record<K, V>một object có khóa K và giá trị V
Exclude<U, X>, Extract<U, X>loại bỏ hoặc giữ lại thành viên của union
NonNullable<T>T không có null và undefined
ReturnType<F>, Parameters<F>kiểu trả về hoặc kiểu tham số của một hàm
Awaited<T>giá trị mà một promise resolve ra

Danh sách đầy đủ có ở trang utility type.

14. Discriminated union là gì?

Là một union các kiểu object có chung một thuộc tính "tag" dạng literal. Kiểm tra tag sẽ thu hẹp giá trị về một thành viên, và một phép kiểm tra never trong nhánh default khiến trình biên dịch báo mọi trường hợp bạn bỏ quên.

15. as và satisfies khác nhau thế nào?

value as T là một type assertion: nó bảo trình biên dịch coi giá trị là T và bỏ qua phần lớn việc kiểm tra. value satisfies T kiểm tra giá trị theo T nhưng giữ nguyên kiểu riêng, chính xác hơn, của giá trị.

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

Khi object đã đầy đủ, satisfies còn giữ kiểu được suy ra của chính nó: khi kiểm tra theo Record<string, string>, biến vẫn biết chính xác nó có những khóa nào, trong khi một chú thích kiểu sẽ mở rộng nó thành khóa chuỗi bất kỳ.

16. Toán tử ! sau một biến làm gì?

Đó là non-null assertion: el! loại null và undefined khỏi kiểu. Nó không sinh ra phép kiểm tra nào, nên nếu giá trị thật sự là null, chương trình sẽ crash ở chỗ dùng nó. Nên ưu tiên ?., ?? hoặc một câu lệnh if tường minh.

17. private và #private khác nhau thế nào?

private chỉ được trình biên dịch thực thi; trong JavaScript đầu ra, thuộc tính đó là một thuộc tính bình thường. #field là private field của JavaScript, được thực thi lúc chạy.

protected hoạt động như private nhưng còn cho phép truy cập từ lớp con, còn readonly cấm gán lại sau khi khởi tạo.

18. Lớp trừu tượng (abstract class) và interface khác nhau thế nào?

Interface chỉ là một kiểu: nó mô tả hình dạng và biến mất khỏi đầu ra. Lớp trừu tượng là một lớp thật nhưng không thể khởi tạo; nó có thể chứa phương thức đã cài đặt, field và hàm khởi tạo bên cạnh các thành viên abstract mà lớp con phải cài đặt. Một lớp có thể implement nhiều interface nhưng chỉ kế thừa được một lớp.

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. Nạp chồng hàm (function overloading) trong TypeScript là gì?

Là nhiều call signature theo sau bởi một phần cài đặt có signature tương thích với tất cả chúng. Nơi gọi chỉ thấy các overload, nên mỗi lời gọi nhận được một kiểu trả về chính xác.

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[]

Khi kiểu trả về không phụ thuộc vào kiểu đối số, một signature duy nhất với tham số kiểu union sẽ đơn giản hơn.

Câu hỏi nâng cao

20. Structural typing là gì?

TypeScript so sánh kiểu theo hình dạng, không theo tên. Giá trị nào có đủ các thuộc tính bắt buộc đều được chấp nhận, kể cả khi nó chưa từng được khai báo với kiểu đó.

color thừa ở đây không sao vì pixel là một biến. Truyền thẳng object literal, show({ x: 3, y: 4, color: "red" }), là lỗi thuộc tính thừa, một phép kiểm tra chỉ áp dụng cho literal mới tạo. Khi hai kiểu cùng hình dạng không được phép lẫn với nhau (một UserId và một OrderId đều là chuỗi), hãy dùng branded type: type UserId = string & { readonly __brand: "UserId" }.

21. Bạn sẽ tự cài đặt Readonly và Partial thế nào?

Bằng mapped type: duyệt qua keyof T và thêm một modifier cho từng thuộc tính.

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. Conditional type và infer là gì?

T extends U ? X : Y chọn một kiểu dựa trên một điều kiện. Bên trong điều kiện, infer khai báo một biến kiểu để bắt lấy một phần của kiểu được khớp. Conditional type tự phân phối qua union.

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)

Thêm nhiều mẫu khác ở trang conditional type.

23. Tệp .d.ts là gì, và declare làm gì?

Tệp khai báo chỉ chứa kiểu cho code tồn tại ở nơi khác, như một thư viện JavaScript hay các API của trình duyệt. declare khẳng định một giá trị tồn tại mà không tạo ra nó: declare const VERSION: string; biên dịch ra không gì cả. Kiểu của thư viện đi kèm trong package hoặc đến từ các package @types/{name}, và tsc sinh tệp .d.ts cho code của chính bạn khi đặt declaration: true.

24. strict làm gì, và những tùy chọn tsconfig nào quan trọng nhất?

"strict": true bật noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables và strictBuiltinIteratorReturn. Trong TypeScript 7, nó được bật mặc định. Những tùy chọn khác người phỏng vấn hay hỏi:

Tùy chọnTrả lời trong một dòng
targetđầu ra dùng phiên bản JavaScript nào
moduleđịnh dạng module của đầu ra: nodenext theo quy tắc của Node, esnext/preserve giữ nguyên import cho bundler
moduleResolutioncách tìm import: nodenext đòi ./file.js trong tệp ES module, bundler chấp nhận ./file
noUncheckedIndexedAccesskiểu của arr[i] có kèm undefined
pathsalias import chỉ dành cho trình kiểm tra kiểu; đầu ra giữ nguyên alias, nên bundler hoặc field imports của Node phải phân giải nó
skipLibCheckbỏ qua kiểm tra các tệp .d.ts, để nhanh hơn
verbatimModuleSyntaximport chỉ dùng làm kiểu phải được đánh dấu type; các import khác được giữ nguyên như khi viết

25. TypeScript 7 là gì?

Là trình biên dịch TypeScript được viết lại bằng Go thành một chương trình native, phát hành dưới dạng phiên bản 7 của package npm typescript. Lệnh vẫn là tsc và ngôn ngữ vẫn như cũ, nhưng nó nhanh hơn nhiều so với trình biên dịch 6.x viết bằng JavaScript: nhóm TypeScript báo cáo tốc độ tăng từ 8 đến 12 lần với các lần build đầy đủ điển hình, nhờ code native và đa luồng dùng chung bộ nhớ. Nó cũng thực thi những thứ đã bị loại bỏ mà bản 6.0 đánh dấu deprecated; ví dụ baseUrl, outFile, moduleResolution: "node10", module: "amd" và alwaysStrict: false giờ là lỗi (TS5102, TS5108) yêu cầu bạn xóa chúng đi.

Câu hỏi thường gặp

Những câu hỏi phỏng vấn TypeScript nào hay gặp nhất?

Những câu xuất hiện nhiều nhất: khác biệt giữa any và unknown, interface và type, generic hoạt động thế nào, thu hẹp kiểu và type guard hoạt động thế nào, các utility type như Partial, Pick và Omit làm gì, và TypeScript có kiểm tra kiểu lúc chạy không (không).

Lập trình viên senior thường được hỏi gì về TypeScript?

Hãy chuẩn bị để viết kiểu chứ không chỉ đọc kiểu: tự cài đặt Readonly hoặc ReturnType bằng mapped type và conditional type, giải thích structural typing và khi nào dùng branded type, thiết kế một discriminated union có kiểm tra đầy đủ các trường hợp, và thảo luận các lựa chọn trong tsconfig như strict, noUncheckedIndexedAccess và module resolution.

Làm sao chuẩn bị cho một buổi phỏng vấn TypeScript?

Viết những chương trình nhỏ với strict được bật và đọc lỗi của trình biên dịch cho tới khi bạn đoán trước được chúng. Hãy giải thích được thu hẹp kiểu, generic có ràng buộc, các utility type chính và vì sao type assertion không an toàn, mỗi thứ kèm một ví dụ hai dòng.

Phỏng vấn React và Angular có hỏi TypeScript không?

Thường là có. Bản thân Angular được viết bằng TypeScript và ứng dụng Angular cũng viết bằng nó, nên phỏng vấn Angular thường mặc định bạn biết TypeScript, kể cả decorator như @Component và access modifier. Phỏng vấn React cho một codebase TypeScript hay yêu cầu bạn khai báo kiểu cho props, state và trình xử lý sự kiện, vốn dùng chính các interface, union và generic trình bày ở đây.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu