Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026

Danh mục

TypeScript iconTypeScript

TypeScript là superset của JavaScript có static typing, compile sang JS.

Lợi ích: phát hiện lỗi tại compile time, IDE support tốt hơn (autocomplete, refactoring), code tự documentation hóa. Không thay thế JS mà enhance nó. Hữu ích nhất ở dự án lớn, team, và codebases cần maintain lâu dài.

Type annotation là khai báo kiểu tường minh cho biến, parameter, return value: let name: string = 'John'.

  • TypeScript cũng có type inference tự suy luận kiểu từ giá trị gán.
  • Annotation thường chỉ cần cho function parameters, return types, và khi inference không đủ thông tin.
typescript
function greet(name: string): string {
  return 'Hi ' + name;
}
let count: number = 0; // annotation thường dư (inference đủ)
let data: unknown;    // cần annotation vì chưa gán

any: tắt type checking, không an toàn, tránh dùng. unknown: type-safe any, phải kiểm tra type trước khi dùng, không thể assign cho typed variable mà không narrow. never: không bao giờ có giá trị, dùng cho hàm luôn throw, hoặc exhaustive checks. unknown >> any về safety.

Union type A | B: value có thể là A hoặc B — ví dụ string | number cho phép cả hai.

  • Intersection type A & B: value phải có tất cả properties của A lẫn B — ví dụ type AdminUser = User & { adminRole: string } tạo type có đủ fields của User cộng thêm adminRole.
  • Union dùng khi một giá trị có thể là nhiều loại (parameter linh hoạt), intersection dùng khi cần combine nhiều interface/type lại.
  • Bẫy: intersection của 2 primitive types không tương thích (như string & number) cho ra type never.

Type assertion overrides TS inference at compile time without any runtime check. value as Type nói với TS "tin tôi, tôi biết kiểu này".

  • Dùng khi TS không thể infer (DOM queries, JSON.parse).
  • Tránh dùng để cast bừa bãi — sẽ mất type safety. as unknown as Type (double assertion) là dấu hiệu code có vấn đề.
typescript
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const data = JSON.parse(json) as UserConfig; // vẫn cần validate runtime

Tuple là array với số lượng và kiểu phần tử cố định.

  • Phần tử có thể có tên.
  • Hỗ trợ optional (?) và rest elements.
  • Thường dùng cho function return nhiều giá trị, CSV rows, React useState.
typescript
type Point = [number, number];
type Entry = [key: string, value: number]; // named elements

const [count, setCount]: [number, React.Dispatch<React.SetStateAction<number>>]
  = useState(0);

// Hoặc đơn giản hơn:
const pair: [string, number] = ['age', 25];

strict: true bật nhiều checks: strictNullChecks (null/undefined không assign cho typed vars), strictFunctionTypes, strictPropertyInitialization, noImplicitAny, noImplicitThis, strictBindCallApply. Luôn bật strict: true cho dự án mới. Khi migrate JS sang TS có thể bật từng flag dần.

Lưu ý: TS 4.4 thêm useUnknownInCatchVariables vào strict — catch variable từ any chuyển thành unknown, bắt buộc narrow trước khi dùng. Điều này thường làm bối rối khi migrate dự án cũ.

  • Interface: có thể extend và merge (declaration merging), chỉ describe object shapes.
  • Type alias: linh hoạt hơn (union, intersection, primitives, tuples, conditional types), không merge.
  • Quy tắc: dùng interface cho public API và khi cần extend, dùng type cho unions/intersections và khi cần advanced type features.

Interface: interface Child extends Parent { ... } và extends nhiều: extends A, B.

  • Type: type Combined = A & B (intersection).
  • Interface có thể extend type và ngược lại.
typescript
interface Animal { name: string }
interface Pet extends Animal { owner: string }
interface Named { label: string }

interface RegisteredPet extends Pet, Named { id: number } // đa extends

// Type intersection tương đương:
type Combined = Pet & Named & { id: number };

extends kiểm tra compatibility (không thể re-declare incompatible property), còn & intersection merge tất cả.

Optional property (prop?: Type) có thể có hoặc không, type là Type | undefined khi access.

Khác với prop: Type | undefined — cái sau vẫn yêu cầu key có mặt khi tạo object. readonly property không thể reassign sau khởi tạo (chỉ compile time). Readonly<T> làm tất cả properties readonly.

typescript
interface User {
  id: number;
  nickname?: string;          // caller có thể bỏ qua
  bio: string | undefined;    // caller phải cung cấp key, nhưng value có thể undefined
  readonly createdAt: Date;   // không thể reassign
}

Partial<T> làm tất cả properties của T thành optional. Required<T> làm tất cả optional thành required.

Cả hai chỉ shallow.

typescript
interface User { id: number; name: string; bio?: string }

// Partial: dùng cho PATCH requests
function updateUser(id: string, data: Partial<User>) { /* ... */ }
updateUser('1', { name: 'New Name' }); // OK, không cần gửi toàn bộ

// Required: enforce tất cả fields sau validation
const validated: Required<User> = { id: 1, name: 'An', bio: 'Dev' };

Record<Keys, Value> tạo object type với specific keys và value type.

  • Khác index signature: Keys có thể là specific union (yêu cầu tất cả keys có mặt, không optional).
  • Rõ ràng hơn và type-safe hơn index signature.
typescript
const routes: Record<'home' | 'about' | 'contact', string> = {
  home: '/',
  about: '/about',
  contact: '/contact', // thiếu key này → Error
};

// Dùng với string key rộng hơn:
const cache: Record<string, unknown> = {};

Pick<T, K> tạo type chỉ với subset of properties K từ T: Pick<User, 'id' | 'name'> chỉ giữ lại id và name. Omit<T, K> là ngược lại: tạo type với tất cả properties trừ K, ví dụ Omit<User, 'password'> để không lộ mật khẩu trong response.

  • Hữu ích cho DTOs, API response shaping, và form state management khi chỉ cần một phần của type.
  • Mẹo: dùng Omit khi cần loại ít field, Pick khi cần giữ ít field — chọn cái nào viết ngắn hơn.

Literal types là type chính xác là một giá trị cụ thể: type Direction = 'left' | 'right' | 'up' | 'down' — chỉ chấp nhận đúng 4 giá trị đó.

  • Kết hợp với union tạo enum-like types mà không có runtime overhead của enum thật.

Ví dụ thực tế: type Status = 'loading' | 'success' | 'error' cho API state.

Type narrowing là thu hẹp type trong code branch dựa trên kiểm tra.

TypeScript tự động narrow trong if/else, switch, ternary.

typescript
function process(val: string | number | Date) {
  if (typeof val === 'string') return val.toUpperCase(); // typeof
  if (val instanceof Date) return val.toISOString();    // instanceof
  return val.toFixed(2);                                 // number
}

// User-defined type guard
function isUser(obj: unknown): obj is User {
  return typeof obj === 'object' && obj !== null && 'id' in obj;
}

! sau expression nói với TS rằng value không phải null/undefined.

  • Hữu ích khi biết chắc value tồn tại nhưng TS không thể prove.
  • Tránh lạm dụng vì có thể gây runtime error.
  • Tốt hơn là dùng optional chaining hoặc explicit null check.
typescript
const el = document.querySelector('.btn')!; // assert not null
el.addEventListener('click', handler);       // OK, no TS error

// Safer alternative:
const el2 = document.querySelector('.btn');
if (el2) el2.addEventListener('click', handler);
  • Enum khai báo tập hợp named constants.
  • Numeric enum tự động assign 0,1,2... có reverse lookup nhưng gây footgun (Direction[0] = 'Up').
  • String enum cần gán tường minh, dễ debug hơn, không có reverse lookup.
typescript
enum Direction { Up, Down } // numeric: Up=0, Down=1
Direction[0] // "Up" — reverse lookup

enum Status { Active = 'ACTIVE', Inactive = 'INACTIVE' } // string

// Lưu ý 2024+: TS community khuyến nghị dùng const object thay enum
// vì tree-shaking tốt hơn và không sinh runtime code thừa
const Direction = { Up: 'up', Down: 'down' } as const;
type Direction = typeof Direction[keyof typeof Direction];

satisfies (TS 4.9) kiểm tra type mà không mở rộng type declaration. Khác as: satisfies vẫn infer type cụ thể nhất có thể và báo lỗi nếu không match.

Ví dụ: const config = {...} satisfies Config - TS check config đúng Config nhưng giữ literal types của values.

as const biến values thành readonly literal types cụ thể nhất. Array thành readonly tuple, object properties thành readonly literal types.

Ví dụ: const dirs = ['left', 'right'] as const tạo type readonly ['left', 'right'] thay string[]. Hữu ích để tạo strongly-typed constants.

Index signature cho phép object có keys không biết trước.

Tất cả explicit properties phải compatible với index signature type.

typescript
interface StringMap { [key: string]: string }
const headers: StringMap = { 'Content-Type': 'application/json' };

// Lưu ý: explicit properties cũng phải match
interface WithLength {
  [key: string]: string;
  length: number; // Error! length phải là string
}

// Record<K,V> thường rõ ràng hơn:
const routes: Record<string, string> = { home: '/', about: '/about' };

Discriminated union là union trong đó mỗi member có common literal property (discriminant).

TypeScript narrow type tự động khi check discriminant.

typescript
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number }
  | { kind: 'triangle'; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.side ** 2;
    case 'triangle':
      return (shape.base * shape.height) / 2;
  }
}

Giúp type-safe handling cases khác nhau, TypeScript sẽ báo lỗi nếu thiếu case.

TypeScript hỗ trợ recursive type aliases.

Hữu ích cho tree structures, nested JSON, linked lists.

typescript
type TreeNode<T> = {
  value: T;
  children: TreeNode<T>[]; // tự tham chiếu
};

// JSON có thể dùng JSON type chuẩn:
type Json = string | number | boolean | null
  | Json[] | { [key: string]: Json };

Interface luôn hỗ trợ recursive (vì là named type), type alias cũng hỗ trợ từ TS 3.7.

TypeScript kiểm tra extra properties khi assign object literal trực tiếp vào typed variable.

Nhưng assign qua biến trung gian không bị check — đây là behavior khá bất ngờ.

typescript
interface Config { host: string; port: number }

// Object literal — excess property check:
const cfg: Config = { host: 'localhost', port: 3000, debug: true }; // Error!

// Qua biến trung gian — không bị check:
const raw = { host: 'localhost', port: 3000, debug: true };
const cfg2: Config = raw; // OK (chỉ check structural compatibility)

Behavior này giúp phát hiện typos trong config objects.

Generics cho phép tạo components (functions, classes, interfaces) hoạt động với nhiều kiểu dữ liệu mà vẫn type-safe.

Thay dùng any, dùng type parameter <T>.

typescript
// Không generic — mất type info
function identity(arg: any): any { return arg; }

// Generic — type-safe
function identity<T>(arg: T): T { return arg; }

const n = identity(42);    // n: number
const s = identity('hi');  // s: string

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

const res: ApiResponse<User[]> = await fetchUsers();

Tái sử dụng code mà không mất type information.

Constraints giới hạn type parameter phải extend một type cụ thể, ngăn dùng với types không compatible.

typescript
// Không có constraint — không access được .length
function getLength<T>(arg: T): number {
  return arg.length; // Error!
}

// Có constraint
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length; // OK
}

// keyof constraint — type-safe property access
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const name = getProperty({ name: 'An', age: 25 }, 'name'); // string

// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

keyof T trả về union type của tất cả keys của T: keyof {a: number, b: string} cho ra 'a' | 'b'.

  • Dùng với generics để type-safe property access: function get<T, K extends keyof T>(obj: T, key: K): T[K] đảm bảo key tồn tại và return đúng kiểu. keyof typeof obj lấy keys của object value (không phải type).
  • Hay gặp trong utility types như Pick, Record, và khi build form hay table components cần truy cập dynamic property một cách an toàn.

Parameters<T> là utility type trích xuất tuple chứa kiểu của tất cả tham số từ một function type T, ví dụ Parameters<typeof Math.max> cho ra [number, number]. ConstructorParameters<T> hoạt động tương tự nhưng dành cho constructor của class, trả về tuple các kiểu tham số khi khởi tạo đối tượng.

  • Bên trong, cả hai đều sử dụng infer keyword trong conditional type: T extends (...args: infer P) => any ? P : never để suy luận ra kiểu tham số.
  • Chúng đặc biệt hữu ích khi cần forward arguments sang hàm khác, tạo wrapper functions, hoặc xây dựng higher-order functions mà vẫn giữ nguyên type safety.
  • Hàm có thể có nhiều type params.
  • Inference hoạt động independently cho mỗi param.
  • Đặt tên rõ ràng (TKey, TValue) thay chỉ T, U, V khi có nhiều params.
typescript
function zip<T, U>(a: T[], b: U[]): [T, U][] {
  return a.map((item, i) => [item, b[i]]);
}
const pairs = zip([1, 2], ['a', 'b']); // [number, string][]

function getEntry<TObj, TKey extends keyof TObj>(obj: TObj, key: TKey): TObj[TKey] {
  return obj[key];
}

Exclude<T, U> loại bỏ khỏi union T những types có thể assign cho U: Exclude<'a'|'b'|'c', 'a'> cho ra 'b'|'c'. Extract<T, U> ngược lại — giữ lại những types assign được cho U: Extract<string|number|boolean, string|number> cho ra string|number.

Ví dụ thực tế: type NonString<T> = Exclude<T, string> để lọc string ra khỏi union.

ReturnType<T> lấy return type của function T mà không cần khai báo lại: type Result = ReturnType<typeof fetchUser> tự động theo dõi khi hàm thay đổi. Awaited<T> unwrap Promise đệ quy: Awaited<Promise<string>> cho ra string, Awaited<Promise<Promise<number>>> cho ra number.

  • Kết hợp cả hai cho async functions: type Data = Awaited<ReturnType<typeof fetchData>>.
  • Rất hữu ích trong large codebases để tránh type duplication và đảm bảo types luôn đồng bộ với implementation.

NonNullable<T> loại bỏ nullundefined khỏi type T: NonNullable<string | null | undefined> cho ra string.

  • Tương đương với Exclude<T, null | undefined>.
  • Hay dùng sau khi đã kiểm tra null ở runtime nhưng TypeScript vẫn chưa tự narrow được — ví dụ kết quả từ Array.find() có type T | undefined, sau khi guard check có thể cast về NonNullable.
  • Với strictNullChecks bật, đây là utility cần thiết để làm việc với optional values an toàn.

Readonly<T> làm tất cả properties của T thành readonly (shallow). ReadonlyArray<T> là array không thể modify (push, pop, sort không available).

Dùng với as const, Redux state, functional programming.

typescript
const nums: ReadonlyArray<number> = [1, 2, 3];
nums.push(4);    // Error! Property 'push' does not exist
nums[0] = 99;   // Error! Index signature is readonly

const state: Readonly<{ count: number }> = { count: 0 };
state.count = 1; // Error!

Deep readonly cần custom type: DeepReadonly<T> với recursive mapped type.

Dùng mapped type iterate qua tất cả keys của T, thêm ? để optional.

typescript
// Partial<T>: tất cả optional
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// Required<T>: dấu - xóa optional modifier
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

// Readonly<T>
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// Pick<T, K>
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// Record<K, V>
type MyRecord<K extends keyof any, V> = {
  [P in K]: V;
};

// Kiểm tra
type User = { id: number; name: string; email?: string };
type PartialUser = MyPartial<User>;
// { id?: number; name?: string; email?: string }

Hiểu cách này giúp tạo custom utility types bất kỳ.

Type guards thu hẹp type trong code branch.

Loại: 1) typeof, 2) instanceof, 3) in operator, 4) equality, 5) user-defined (function với return type is Type), 6) assertion functions.

typescript
function process(val: string | number | null) {
  if (typeof val === 'string') return val.toUpperCase();
  if (typeof val === 'number') return val.toFixed(2);
  return 'null';
}

// User-defined guard (most powerful)
function isUser(obj: unknown): obj is { id: string; name: string } {
  return typeof obj === 'object' && obj !== null && 'id' in obj;
}

Function type với return type param is Type.

Khi function return true, TS narrow type của param.

typescript
// Type predicate
function isString(val: unknown): val is string {
  return typeof val === 'string';
}

// Sử dụng
function process(val: string | number) {
  if (isString(val)) {
    val.toUpperCase(); // val: string
  } else {
    val.toFixed(2);   // val: number
  }
}

// Thực tế: kiểm tra object shape
interface Cat { meow(): void }
interface Dog { bark(): void }

function isCat(animal: Cat | Dog): animal is Cat {
  return 'meow' in animal;
}

// Array filter với type guard
const items: (string | null)[] = ['a', null, 'b'];
const strings = items.filter((x): x is string => x !== null);
// strings: string[]

Nguy hiểm: TS tin type predicate hoàn toàn, implementation sai sẽ gây runtime bug mà không có compile error.

Declaration files chứa type declarations không có implementation (ambient declarations).

  • Dùng để type third-party libraries không viết bằng TS. @types/ packages trên npm.
  • Tự viết .d.ts khi cần declare types cho JS code hoặc global variables.
typescript
// Khai báo SVG imports (pattern phổ biến với Vite/Webpack)
declare module '*.svg' {
  const content: string;
  export default content;
}

// Mở rộng global:
declare global {
  interface Window { analytics: Analytics; }
}

Trong tsconfig.json, strict: true là option quan trọng nhất vì nó bật toàn bộ strict type-checking bao gồm strictNullChecks, noImplicitAny và strictFunctionTypes. target xác định phiên bản ES output (ví dụ ES2020), module chọn hệ thống module (ESNext, CommonJS), và moduleResolution quyết định cách TypeScript tìm kiếm file khi import (node, bundler).

Ngoài ra, paths cho phép cấu hình path aliases (ví dụ @/components), isolatedModules bắt buộc khi dùng bundlers như Vite hoặc esbuild vì chúng compile từng file riêng lẻ, và sourceMap: true giúp debug bằng cách map code đã compile về source TypeScript gốc.

useState: useState<Type>(init) khi init không đủ thông tin. useRef: useRef<HTMLInputElement>(null) cho DOM refs, useRef<number>(0) cho mutable values (không trigger re-render). useReducer: type actions với discriminated unions.

typescript
const [user, setUser] = useState<User | null>(null);

const inputRef = useRef<HTMLInputElement>(null);  // DOM ref
const timerRef = useRef<number>(0);              // mutable value

// Custom hook với explicit return type:
function useCounter(init = 0): [number, () => void] {
  const [count, setCount] = useState(init);
  return [count, () => setCount(c => c + 1)];
}

React.ChangeEvent<HTMLInputElement> cho onChange, React.MouseEvent<HTMLButtonElement> cho onClick, React.FormEvent<HTMLFormElement> cho onSubmit.

Nếu không nhớ chính xác: hover lên event trong IDE, hoặc dùng React.SyntheticEvent rộng hơn.

typescript
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  setValue(e.target.value);
}

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  // ...
}

paths trong tsconfig.json map import paths để tránh relative paths dài.

Cần configure cả bundler vì TS compiler chỉ handle types, không transform imports.

json
// tsconfig.json
{ "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
typescript
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
  resolve: { alias: { '@': '/src' } }
});
// Dùng: import { Button } from '@/components/Button'