TypeScript

Type System cơ bản

Primitive, object, array, tuple, enum, union, intersection, literal, narrowing, type guard, any vs unknown vs never.

TypeScript 6.0 & 7.0 — Những thay đổi quan trọng

TS 6.0 và 7.0 mang đến nhiều thay đổi mặc định quan trọng ảnh hưởng đến cách type system hoạt động. Dưới đây là những điểm cần nắm khi phỏng vấn hoặc nâng cấp dự án.

strict: true là mặc định (TS 6.0+)

Từ TS 6.0, tất cả dự án không khai báo tường minh strict trong tsconfig.json sẽ tự động nhận strict: true. Điều này bao gồm toàn bộ 8 cờ con:

  • noImplicitAny — cấm any ngầm định
  • strictNullChecksnull / undefined là type riêng
  • strictFunctionTypes — kiểm tra function type chặt hơn
  • strictBindCallApply — kiểm tra .bind, .call, .apply
  • strictPropertyInitialization — property class phải được khởi tạo
  • alwaysStrict — emit "use strict"
  • useUnknownInCatchVariablescatch(e)e: unknown
  • noImplicitThis — cấm this ngầm định là any
Nếu dự án cũ không có "strict": true tường minh, sau khi nâng cấp TS 6.0 có thể gặp hàng loạt lỗi type mới. Luôn chạy tsc --noEmit để kiểm tra trước khi nâng cấp.

types: [] là mặc định (TS 6.0+)

Trước đây TS tự động quét tất cả package @types/* trong node_modules. Từ TS 6.0, mặc định không còn auto-discovery nữa — phải khai báo tường minh:

// tsconfig.json
{
  "compilerOptions": {
    "types": ["node"]  // nếu cần process, fs, Buffer...
  }
}
Câu PV:"Sau khi nâng cấp TS 6.0, process.env, describe, expect bị lỗi — tại sao?"

"Vì TS 6.0 mặc định types: [], không còn auto-discovery @types/*. Phải thêm "types": ["node"] (cho process) hoặc "types": ["jest"] (cho describe, expect) vào tsconfig.json."

stableTypeOrdering: true — bắt buộc từ TS 7.0

  • Thứ tự type được đảm bảo deterministic (xác định) giữa các lần chạy
  • Cần thiết cho parallel type-checking — các worker khác nhau phải sinh ra kết quả giống hệt nhau
  • Từ TS 7.0, không thể tắt tùy chọn này
  • Có thể làm lộ ra các lỗi type tinh vi trước đây bị ẩn do phụ thuộc vào thứ tự type không xác định
Nếu sau khi nâng cấp TS 7.0, code đột nhiên báo lỗi type mà trước đây không có → có thể do stableTypeOrdering làm lộ bug ẩn trong generic phức tạp, conditional type, hoặc type inference phụ thuộc thứ tự.

1. Primitive types

let s: string = "hello";
let n: number = 42;            // không có int/float — số JS là double 64-bit
let b: boolean = true;
let big: bigint = 100n;
let sym: symbol = Symbol("x");
let u: undefined = undefined;
let nu: null = null;
let v: void;                   // chỉ cho return của function không return gì
let never_: never;             // không bao giờ có giá trị (function throw, infinite loop)
Câu PV thường gặp:"nullundefined khác nhau ra sao trong TS?"

"Cùng là 'không có giá trị' nhưng khác semantic: undefined = 'chưa được gán', null = 'có ý định gán = không gì'. Với strictNullChecks bật, 2 type này khác nhau, phải khai báo string | null hoặc string | undefined rõ ràng."

2. Object, array, tuple

// Object
let user: { id: number; name: string; age?: number } = {
  id: 1, name: "Alice"
};

// Array
let nums: number[] = [1, 2, 3];
let nums2: Array<number> = [1, 2, 3];    // generic form, tương đương

// Tuple — fixed length + position-typed
let pair: [string, number] = ["a", 1];
let triple: [string, number, boolean] = ["a", 1, true];

// Rest in tuple
let log: [Date, ...string[]] = [new Date(), "info", "user logged in"];

// Readonly array
let ro: readonly number[] = [1, 2, 3];
ro.push(4);                              // ❌ error
let ro2: ReadonlyArray<number> = [1, 2, 3];

Map / WeakMap getOrInsert (TS 6.0)

const map = new Map<string, number>();
const val = map.getOrInsert("key", () => 42); // number — type-safe upsert
getOrInsert(key, factory) — nếu key tồn tại thì trả về value, nếu chưa thì gọi factory() để tạo value mới và insert. Type-safe hơn pattern if (!map.has(k)) map.set(k, v) vì TS biết chính xác return type.

3. Literal types & Union

// Literal type — chỉ nhận giá trị cụ thể
let status: "active" | "inactive" | "pending";
status = "active";        // OK
status = "deleted";       // ❌ error

let dice: 1 | 2 | 3 | 4 | 5 | 6;

// Union — nhiều type
type Id = string | number;
function getUser(id: Id) { ... }

// Discriminated union (tagged union) — quan trọng
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "rect"; w: number; h: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;   // narrowed → có radius
    case "square": return s.side ** 2;
    case "rect":   return s.w * s.h;
  }
}
Discriminated Union là pattern quan trọng nhất TS — gần như chắc chắn hỏi.Cấu trúc: mỗi member union có 1 field "discriminator" giá trị literal. switch/if trên field này → TS tự narrow type, autocomplete chính xác.

4. Intersection — &

// Trộn nhiều type
type WithId = { id: number };
type WithName = { name: string };
type User = WithId & WithName;     // { id: number; name: string }

// Generic intersection
type WithTimestamps<T> = T & { createdAt: Date; updatedAt: Date };
type UserRecord = WithTimestamps<User>;
Union | ≠ Intersection &:
  • A | B — value là A HOẶC B (set OR).
  • A & B — value có đủ field A VÀ B (set AND).
Nghịch đảo logic của set theory.

5. type vs interface — chọn gì?

// type alias — flexible
type User = { id: number; name: string };
type Id = string | number;                          // union
type Pair<T> = [T, T];                              // tuple

// interface — extensible
interface User {
  id: number;
  name: string;
}
interface User { age: number; }                     // declaration merge — User giờ có 3 field

interface Admin extends User {
  role: string;
}
interfacetype
Object shape
Union, intersection, primitive
Tuple, mapped type
Declaration merging
Performance (cho object)Hơi nhanh hơn
Khuyến nghịObject public APIUnion, alias, utility
Câu PV:"type vs interface?"

"Cả 2 mô tả shape object. interface chuyên cho object + class hợp đồng, hỗ trợ extends + declaration merging. type flexible hơn — làm được union, intersection, tuple, mapped type.

Rule of thumb: object public API dùng interface, alias / union / utility dùng type. Hoặc đơn giản — dùng type cho mọi thứ, không sai."

6. any vs unknown vs never

any
THE escape hatch
Tắt mọi type check. Như JS thuần. Dùng càng ít càng tốt — sai mục đích của TS.
unknown
any an toàn
Nhận mọi giá trị nhưng phải narrow trước khi dùng. Type-safe.
never
không bao giờ
Không có giá trị nào thuộc type này. Function throw / infinite loop có return type never.
let x: any = 1;
x.toUpperCase();         // OK compile, BANG runtime

let y: unknown = 1;
y.toUpperCase();         // ❌ error — phải narrow
if (typeof y === "string") {
  y.toUpperCase();       // OK — narrowed thành string
}

function fail(msg: string): never {
  throw new Error(msg);
}

function exhaustive(s: never): never {
  throw new Error(`Unhandled: ${s}`);
}
never cho exhaustive check — pattern cực hay trong PV:
type Shape = { kind: "circle" } | { kind: "square" };

function area(s: Shape) {
  switch (s.kind) {
    case "circle": return ...;
    case "square": return ...;
    default:
      const _exhaustive: never = s;  // ⭐ Nếu Shape thêm "rect" mà quên handle
      throw new Error(_exhaustive);  //    → compile error tại đây
  }
}

7. Type narrowing — sức mạnh TS

function format(x: string | number) {
  // typeof narrowing
  if (typeof x === "string") {
    return x.toUpperCase();    // narrowed → string
  }
  return x.toFixed(2);         // narrowed → number
}

// instanceof
class Dog {}
class Cat {}
function speak(a: Dog | Cat) {
  if (a instanceof Dog) { /* a: Dog */ }
}

// in operator
type Bird = { fly(): void };
type Fish = { swim(): void };
function move(a: Bird | Fish) {
  if ("fly" in a) a.fly();
  else a.swim();
}

// Truthiness narrowing
function f(s: string | null | undefined) {
  if (s) {                     // s narrowed thành string non-empty
    s.length;
  }
}

// Equality narrowing
function g(x: string | number, y: string | boolean) {
  if (x === y) {               // 2 type chung chỉ có string → cả 2 narrowed string
    x.toUpperCase();
    y.toUpperCase();
  }
}

Custom type guard

// Function trả `param is Type` → TS tin để narrow
function isString(x: unknown): x is string {
  return typeof x === "string";
}

const arr: unknown[] = ["a", 1, "b"];
const strings = arr.filter(isString);    // strings: string[] (auto narrow)
x is Type syntax = type predicate. Cực hay trong PV — phân biệt được fresher / junior:
function isUser(obj: unknown): obj is User {
  return typeof obj === "object" && obj !== null && "id" in obj && "name" in obj;
}

Template literal type & Unicode (cải thiện TS 7.0)

TS 7.0 xử lý đúng Unicode surrogate pairs khi infer template literal type:

type HeadTail<S> = S extends `${infer Head}${infer Tail}` ? [Head, Tail] : never;
type Result = HeadTail<"😀abc">;
// TS 7.0: ["😀", "abc"]  ✅ — tách đúng emoji (surrogate pair)
// TS cũ:  ["\ud83d", "\ude00abc"] ❌ — tách vỡ surrogate pair
Trước TS 7.0, template literal type inference xử lý từng UTF-16 code unit, dẫn đến tách vỡ các ký tự nằm ngoài BMP (emoji, một số ký tự CJK...). TS 7.0 xử lý đúng Unicode code point.

8. Assertion vs Narrowing

// Assertion — TELL compiler, không CHECK
const el = document.getElementById("app") as HTMLDivElement;
const x = unknownThing as User;          // unsafe — bạn chịu trách nhiệm

// Non-null assertion (!)
const el2 = document.getElementById("app")!;     // = "I'm sure it's not null"

// satisfies (TS 4.9+) — check mà giữ literal narrow
const palette = {
  red: [255, 0, 0],
  green: "#0f0"
} satisfies Record<string, [number, number, number] | string>;
// palette.red vẫn là [number, number, number], không bị widen
as! là escape hatch — bypass type system. Dùng đúng lúc, đừng lạm dụng. Khi bạn as sai → runtime bug.

9. Function types

// Function declaration
function add(a: number, b: number): number {
  return a + b;
}

// Function type
type Adder = (a: number, b: number) => number;
const sub: Adder = (a, b) => a - b;

// Optional + default + rest
function greet(name: string, greeting = "Hi", ...others: string[]): string {
  return `${greeting}, ${name} & ${others.join(",")}`;
}

// Overload
function len(x: string): number;
function len(x: any[]): number;
function len(x: string | any[]): number {
  return x.length;
}

// Constructor type
type Ctor<T> = new (...args: any[]) => T;
function create<T>(C: Ctor<T>): T { return new C(); }

Giảm context sensitivity cho function type inference (TS 6.0)

Từ TS 6.0, function không có tham số this sẽ có reduced context sensitivity — type inference đơn giản và dễ đoán hơn:

// Trước TS 6.0: context-sensitive inference có thể gây bất ngờ
// TS 6.0+: inference đơn giản hơn, dễ debug hơn
const fn = (x) => x; // inference rõ ràng, không phụ thuộc ngữ cảnh gọi
Thay đổi này giúp type inference của function trở nên predictable hơn, đặc biệt khi làm việc với generic và higher-order function. Code cũ phụ thuộc vào context-sensitive behavior có thể cần thêm type annotation.

10. Enum vs const + as const

// Classic enum — emit code, có thể trick (Reverse mapping)
enum Status { Active, Inactive, Pending }   // = 0, 1, 2
let s: Status = Status.Active;

// String enum
enum Color { Red = "RED", Green = "GREEN" }

// const enum — inline, không emit (chỉ literal khi build)
const enum Direction { Up, Down, Left, Right }
Enum đang bị deprecate dần. TS 6.0 đã loại bỏ preserveConstEnums, và const + as const càng được khuyến khích mạnh mẽ hơn để thay thế hoàn toàn enum:
const Status = {
  Active: "active",
  Inactive: "inactive",
  Pending: "pending"
} as const;

type Status = typeof Status[keyof typeof Status];
//     ^ "active" | "inactive" | "pending"
Lý do: enum emit code runtime, khó tree-shake; const + as const thuần type, không phình bundle.

11. Object methods & this

type Person = {
  name: string;
  greet(this: Person): string;       // ⭐ this typed
};

const p: Person = {
  name: "Alice",
  greet() { return `Hi, ${this.name}`; }
};

// Class field arrow vs method — `this` khác
class Counter {
  count = 0;
  inc() { this.count++; }              // bind dynamic — this có thể lost
  incArrow = () => { this.count++; };  // bind lexical — this luôn = instance
}
⭐ Câu PV React: "Class method bind this ra sao?" → dùng arrow function field hoặc .bind(this) trong constructor.

12. Index signature

// Bất kỳ key string → value type X
type Dict<T> = { [key: string]: T };
const cache: Dict<number> = { a: 1, b: 2 };

// Mix với known key
type Config = {
  apiUrl: string;
  timeout: number;
  [extra: string]: string | number;
};

// Restricted key — Record (utility type)
type Roles = Record<"admin" | "user" | "guest", string>;
// = { admin: string; user: string; guest: string }

13. Subpath imports #/ (TS 6.0)

TS 6.0 hỗ trợ subpath imports — alias import chuẩn của Node.js, không cần config paths trong tsconfig.json:

// package.json
{
  "imports": {
    "#utils/*": "./src/utils/*",
    "#components/*": "./src/components/*"
  }
}
// Dùng ở bất kỳ file nào trong project — không cần relative path dài
import { helper } from "#utils/helper";
import { Button } from "#components/Button";
// TypeScript tự động resolve và check type
#/ imports vs paths trong tsconfig:
  • #/ là chuẩn Node.js — hoạt động cả ở runtime lẫn type-check
  • Không cần config trong tsconfig.json
  • Bắt đầu bằng # để phân biệt với package imports thông thường
  • Thay thế dần cho baseUrl + paths (vốn đã bị deprecate từ TS 6.0)

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.