Skip to content

Phụ lục — Bẫy phỏng vấn DSA thường gặp

Trong phỏng vấn, bạn không thua vì thiếu kiến thức — bạn thua vì rơi vào bẫy. Interviewer thường đặt câu hỏi mà đáp án "hiển nhiên" lại sai. Phụ lục này tổng hợp 30+ bẫy phỏng vấn kinh điển từ mỗi topic family trong Phase 1, giúp bạn nhận diện trước khi bước vào phòng phỏng vấn.

Đọc chậm. Mỗi bẫy đều là một bài học mà ai đó đã trả giá bằng một offer bị từ chối.

🎯 Mục tiêu

Sau khi đọc xong, bạn sẽ:

  • Nhận diện 30+ bẫy phỏng vấn phổ biến nhất trong DSA
  • Hiểu TẠI SAO mỗi bẫy sai (không chỉ biết đáp án đúng)
  • Chuẩn bị câu trả lời chắc chắn cho từng trap trước khi bước vào phòng phỏng vấn

📖 Cách sử dụng trang này

Trang này không phải để đọc một lần. Đây là tài liệu reference — cách dùng hiệu quả nhất:

  1. Lần đầu: Đọc toàn bộ, đánh dấu những trap bạn đã từng mắc
  2. Trước phỏng vấn: Review bảng Top 10 + Cheat Sheet ở cuối trang
  3. Sau khi fail interview: Quay lại xem bạn rơi vào trap nào, ghi chú vào sổ tay
  4. Khi ôn topic cụ thể: Nhảy thẳng đến section tương ứng (Sorting, Searching, v.v.)

Mỗi bảng trap có 3 cột quan trọng: Bẫy (cái gì sai), Tại sao sai (root cause), Cách đúng (câu trả lời chuẩn). Đừng chỉ nhớ cách đúng — hiểu tại sao sai mới giúp bạn tránh được biến thể của trap.


🫧 Sorting Traps

Sorting là topic "dễ ăn điểm" — nhưng cũng là nơi interviewer cài bẫy nhiều nhất, vì ứng viên hay tự tin quá mức.

#BẫyTại sao saiCách đúng
1"Bubble Sort luôn O(n²)"Với swapped flag, best case = O(n) trên sorted data. Nhiều ứng viên quên optimized variantPhân biệt best / average / worst case. Bubble Sort optimized: best O(n), average & worst O(n²)
2"Selection Sort is stable"Swap phá vỡ relative order. VD: [5a, 5b, 3] → swap 5a với 3 → [3, 5b, 5a] — 5a và 5b đảo vị tríSelection Sort KHÔNG stable. Muốn stable O(n²) → dùng Insertion Sort
3"Hai vòng lặp = O(n²)"Hai vòng lặp có thể là O(n) nếu tổng iterations ≤ n. VD: two pointers, sliding window — hai con trỏ nhưng mỗi element chỉ được visit tối đa 2 lầnĐếm TỔNG operations, không đếm số vòng lặp. Amortized analysis là chìa khóa
4"Quick Sort luôn O(n log n)"Pivot xấu (first element trên sorted input) → mỗi partition chỉ giảm 1 element → O(n²)Random pivot hoặc median-of-3. Luôn nói: "average O(n log n), worst O(n²) nếu pivot xấu"
5"Heap Sort nhanh hơn Quick Sort"Worst-case tốt hơn (O(n log n) guaranteed), nhưng average CHẬM hơn do cache locality kém — heap access nhảy lung tung trong memoryQuick Sort nhanh hơn in practice nhờ sequential memory access. Heap Sort chỉ thắng khi cần worst-case guarantee
6"Timsort chỉ là Merge Sort"Timsort exploit natural runs (dãy đã sorted sẵn) + dùng Insertion Sort cho small arrays (< 64 elements) + galloping mode cho mergeTimsort là hybrid adaptive sort, default trong Python và Java. Phân biệt rõ với pure Merge Sort
7"Counting Sort luôn O(n)"Complexity thực tế là O(n + k) với k = range of values. Nếu k >> n (VD: sort 10 số trong range 0–10⁹) thì cực kỳ chậm và tốn memoryChỉ dùng Counting Sort khi k ≤ n hoặc k là hằng số nhỏ

Mẹo phỏng vấn

Khi interviewer hỏi về sorting, luôn nói ba complexity: best, average, worst. Đừng chỉ nói "O(n log n)" — đó là câu trả lời incomplete.


🔍 Searching Traps

Binary search nhìn đơn giản nhưng là thuật toán có tỷ lệ bug cao nhất khi implement. Jon Bentley từng nói: chỉ 10% lập trình viên có thể viết binary search đúng lần đầu.

#BẫyTại sao saiCách đúng
1mid = (lo + hi) / 2Integer overflow khi lo + hi > INT_MAX. VD: lo = 2×10⁹, hi = 2×10⁹lo + hi tràn sốmid = lo + (hi - lo) / 2 — phép trừ trước, không bao giờ overflow
2"Binary Search chỉ cho sorted array"Binary search on answer: khi answer space là monotonic (nếu x thỏa thì x+1 cũng thỏa), ta binary search trên kết quảHọc pattern search-on-answer: "tìm giá trị nhỏ nhất thỏa điều kiện"
3while (lo < hi) với inclusive bounds [lo, hi]Bỏ sót element cuối khi lo == hi — element đó không bao giờ được checkwhile (lo <= hi) cho inclusive bounds [lo, hi]. Hoặc dùng while (lo < hi) với exclusive right bound [lo, hi)
4hi = mid với while (lo <= hi)Infinite loop khi lo == hi: mid = lo, hi = mid = lo, loop không bao giờ thoátMatch loop condition với update logic. while (lo <= hi)hi = mid - 1. while (lo < hi)hi = mid
5"Linear Search luôn tệ hơn Binary Search"Single query trên unsorted data: sort O(n log n) + binary O(log n) = tổng O(n log n) > linear O(n)Linear Search tốt cho single query / unsorted data. Binary Search chỉ win khi sort 1 lần + query nhiều lần

Binary Search Template

Chọn MỘT template và dùng nhất quán. Đừng trộn logic giữa các variant — đó là nguồn gốc của 90% bug binary search.


📦 Data Structure Traps

Data structures là nền tảng, nhưng nhiều ứng viên hiểu sai complexity vì không phân biệt được theoretical vs amortized vs worst-case.

#BẫyTại sao saiCách đúng
1"Linked List insert O(1)"Chỉ O(1) khi ĐÃ CÓ reference đến node trước vị trí insert. Nếu phải tìm node đó → traverse O(n) trướcClarify: insert at known position = O(1), find-then-insert = O(n). Luôn hỏi interviewer: "insert ở đâu?"
2"ArrayList.add() is O(n)"Amortized O(1) vì resize (copy toàn bộ array) chỉ xảy ra khi array đầy — trung bình mỗi add rất rẻHiểu amortized analysis: chi phí resize được "trải đều" cho tất cả operations trước đó
3"Stack overflow = bug trong code"Có thể do recursion quá sâu trên input lớn — code đúng logic nhưng stack frame vượt limitDùng iterative approach (explicit stack) cho input lớn. Python default recursion limit = 1000
4"HashMap is O(1) always"Worst-case O(n) khi tất cả keys hash vào cùng một bucket → degenerate thành linked listAverage O(1), worst O(n). Java 8+ chuyển bucket sang red-black tree khi > 8 elements → worst O(log n)
5"Queue = chỉ FIFO"Deque (double-ended), Priority Queue, Circular Buffer, Monotonic Queue đều là queue variants với behavior khác nhauBiết các biến thể và khi nào dùng: FIFO cho BFS, Priority Queue cho Dijkstra, Monotonic Queue cho sliding window max
6"Array tốt hơn Linked List mọi trường hợp"LRU Cache: doubly linked list + hash map = O(1) access + O(1) eviction. Array không thể làm điều này efficientlyLinked List tốt cho specific patterns: LRU Cache, undo/redo, polynomial arithmetic, music playlist

Khi nào nói "O(1)"

Luôn clarify: O(1) amortized hay O(1) worst-case? HashMap get là O(1) amortized nhưng O(n) worst-case. Array access là O(1) worst-case thực sự.


🕸️ Graph Traps

Graph problems chiếm ~25% câu hỏi ở Big Tech interviews. Và graph traps là những bẫy nguy hiểm nhất — vì code chạy đúng trên test case nhỏ nhưng sai hoặc TLE trên input lớn.

#BẫyTại sao saiCách đúng
1Quên visited set trong BFS/DFSInfinite loop trên cyclic graph — node A visit B, B visit A, lặp mãiLUÔN dùng visited set. Đây là rule #1 của graph traversal, không có ngoại lệ (trừ DAG đã confirm)
2"Dijkstra cho mọi graph"Negative weights phá vỡ greedy assumption: Dijkstra finalize node sớm, nhưng negative edge có thể tạo path ngắn hơn sau đóNegative weights → Bellman-Ford O(VE). Negative cycle detection cũng cần Bellman-Ford
3"BFS tìm shortest path mọi graph"Chỉ đúng cho UNWEIGHTED graph (mỗi edge weight = 1). Weighted graph → BFS cho sai kết quảUnweighted → BFS. Weighted (non-negative) → Dijkstra. Weighted (có negative) → Bellman-Ford
4"DFS cho shortest path"DFS explore theo chiều sâu, không guarantee tìm path ngắn nhất — nó tìm một path, không phải optimal pathDFS cho: existence check, cycle detection, topological sort, connected components. Shortest path → BFS hoặc Dijkstra
5"Adjacency matrix cho mọi graph"Sparse graph (V=10⁶, E=10⁶): matrix tốn O(V²) = 10¹² cells. Adjacency list chỉ tốn O(V + E) = 2×10⁶Adj list cho sparse graph (hầu hết real-world graphs). Matrix cho dense graph hoặc khi cần check edge existence O(1)
6Dijkstra không skip visited nodesXử lý lại nodes đã finalized → chậm (có thể TLE) hoặc sai kết quả trong edge casesThêm if node in visited: continue ngay sau pop từ priority queue. Skip nodes đã có shortest distance
7"BFS space = O(V)"Đúng nhưng misleading. BFS queue có thể chứa O(V) nodes cùng lúc ở worst case (star graph — tất cả nodes connect đến 1 center)Hiểu space complexity thực tế: BFS trên star graph → queue size = V-1. Trên binary tree balanced → queue size = V/2 ở level cuối

Bẫy chết người

Quên visited set + cyclic graph = infinite loop trong phỏng vấn. Đây là lỗi #1 khiến ứng viên fail graph questions. Viết visited set TRƯỚC KHI viết BFS/DFS logic.


🧩 Dynamic Programming Traps

DP là topic khó nhất trong interview — không phải vì thuật toán phức tạp, mà vì sai một chi tiết nhỏ = sai toàn bộ kết quả. Không có "gần đúng" trong DP.

#BẫyTại sao saiCách đúng
1Quên base caseRecursion không bao giờ dừng → stack overflow hoặc wrong answer vì return garbage valueXác định base cases TRƯỚC KHI viết recurrence. VD: dp[0] = 0, dp[1] = 1 cho Fibonacci
2Memoize nhưng quên check cacheCode có memo dict/array nhưng không check trước khi compute → vẫn O(2ⁿ) vì tính lại mọi subproblemPattern: if key in memo: return memo[key] TRƯỚC mọi computation. Luôn check trước, tính sau
3"Mọi recursion đều là DP"DP cần overlapping subproblems. Binary search là recursive nhưng KHÔNG phải DP — mỗi subproblem chỉ xuất hiện 1 lầnKiểm tra 2 trụ cột: (1) Optimal substructure, (2) Overlapping subproblems. Thiếu một → không phải DP
4Sai state definitionState không capture đủ information → wrong answer. VD: knapsack chỉ track value mà quên track remaining capacityState phải chứa đủ information để quyết định mọi subproblem. Thêm dimension nếu cần
5Off-by-one trong dp arraydp = [0] * n thay vì dp = [0] * (n + 1) → index out of bounds khi access dp[n]Size = n + 1 khi dp[i] đại diện cho "dùng i items" (i từ 0 đến n). Vẽ bảng trước khi code
6"Top-down luôn tốt hơn bottom-up"Top-down (memoization): stack overflow cho n lớn, cache behavior kém. Bottom-up (tabulation): no stack, sequential memory accessBottom-up cho n lớn (> 10⁴), top-down khi logic phức tạp hoặc chỉ cần một phần state space

DP Checklist trước khi code

  1. ✅ Xác định state — cần bao nhiêu dimensions?
  2. ✅ Viết recurrence relation trên giấy
  3. ✅ Xác định base cases — mọi recursion path đều phải reach base case
  4. ✅ Chọn top-down hay bottom-up dựa trên constraint
  5. ✅ Kiểm tra array size — off-by-one là bug phổ biến nhất

⚙️ General Interview Traps

Những bẫy này không thuộc topic cụ thể nào — chúng là meta-traps về cách bạn approach interview. Rơi vào đây thì dù thuật toán đúng, bạn vẫn fail.

#BẫyTại sao saiCách đúng
1Code ngay không hỏi clarifying questionsCó thể giải sai bài hoặc miss edge cases mà interviewer cố tình giấuHỏi trước khi code: input range? duplicates? sorted? negative numbers? empty input?
2Premature optimizationOptimize O(n²) → O(n log n) trong khi constraint n ≤ 100 — lãng phí thời gian, tăng complexity, dễ bugGiải brute-force đúng trước → analyze complexity → optimize chỉ khi cần
3Bỏ qua edge casesEmpty array, single element, all same values, INT_MAX, INT_MIN, negative numbers — mỗi cái là một bug tiềm ẩnLuôn test: [], [x], [x, x, x], extreme values. Nói ra edge cases trước khi interviewer hỏi
4Không nói complexity analysisInterviewer muốn nghe bạn phân tích — code đúng mà không analyze = chỉ đạt 50% điểmState time + space complexity cho EVERY solution. So sánh trade-offs giữa approaches
5"Tôi biết cách giải nhưng không code được"Kiến thức ≠ kỹ năng implementation. Biết Quick Sort work nhưng không viết được partition = failPractice coding, không chỉ đọc lý thuyết. Mỗi algorithm, viết ít nhất 3 lần từ đầu
6Không communicate trong lúc codeIm lặng 20 phút → interviewer không biết bạn đang nghĩ gì → không thể cho hint hoặc partial creditThink aloud: nói approach, trade-offs, tại sao chọn data structure này. Interviewer đánh giá process, không chỉ result

Quy tắc 5 phút

Nếu bạn stuck hơn 5 phút mà không tiến triển — nói ra. "I'm stuck on X, let me think about a different approach" tốt hơn im lặng. Interviewer sẽ cho hint, và nhận hint không phải là điểm trừ.


🏆 Top 10 Interview Traps — Xếp hạng theo tần suất

Từ 30+ bẫy ở trên, đây là 10 bẫy xuất hiện nhiều nhất trong phỏng vấn thực tế, xếp hạng theo tần suất ứng viên mắc phải:

HạngBẫyTopicMức độ nguy hiểm
🥇 1Integer overflow trong binary search mid = (lo + hi) / 2Searching🔴 Critical
🥈 2Quên visited set trong graph traversalGraph🔴 Critical
🥉 3Dijkstra với negative weights — vẫn dùng Dijkstra khi graph có negative edgesGraph🔴 Critical
4Quên base case trong DP — recursion chạy mãi không dừngDP🔴 Critical
5Selection Sort is stable — trả lời sai khi interviewer hỏi stable sortSorting🟡 High
6while (lo < hi) vs while (lo <= hi)mismatch loop condition với update logicSearching🟡 High
7HashMap "always O(1)" — quên worst-case O(n)Data Structures🟡 High
8Hai nested loops = O(n²) — không phải lúc nào cũng đúng (two pointers, sliding window)Sorting / General🟡 High
9Quick Sort worst-case trên sorted input với first-element pivotSorting🟠 Medium
10Memoization không check cache — có memo nhưng quên if key in memoDP🟠 Medium

💡 Pro tip: In danh sách này ra, dán lên tường phòng ngủ, review mỗi tối trước ngày phỏng vấn. Nghiêm túc.

Tại sao top 3 đều là "Critical"?

Vì cả 3 đều gây ra kết quả sai hoàn toàn (không phải chậm, không phải suboptimal — mà SAI):

  • Integer overflow: mid sai → search sai vùng → trả về -1 khi answer tồn tại. Trong production: crash, security vulnerability
  • Quên visited: Infinite loop → chương trình treo → timeout trong interview, OOM trong production
  • Dijkstra + negative weights: Trả về shortest path sai — Dijkstra vẫn return giá trị, nhưng giá trị đó không phải minimum. Âm thầm sai là nguy hiểm nhất

Còn các trap hạng 5-10 thường gây suboptimal answer hoặc incomplete analysis — vẫn mất điểm nhưng không fatal.


🧪 Practice: Kiểm tra kiến thức

🧠 Quiz — Scenario Choice

Tình huống: Interviewer cho bạn một graph có negative weights và yêu cầu tìm shortest path. Bạn đề xuất Dijkstra. Interviewer nhíu mày hỏi: "Bạn chắc chưa?"

Bạn nên làm gì?

A. Giữ nguyên Dijkstra — worst-case vẫn cho kết quả gần đúng

B. Chuyển sang Bellman-Ford — vì Dijkstra không handle negative weights

C. Hỏi thêm: "Graph có negative cycle không?" — rồi quyết định Bellman-Ford hoặc báo impossible

D. Dùng BFS — vì BFS luôn tìm shortest path

💡 Đáp án & Giải thích

Đáp án đúng: C

  • A sai: Dijkstra với negative weights cho kết quả SAI (không phải gần đúng — hoàn toàn sai) vì greedy assumption bị phá vỡ
  • B đúng một phần: Bellman-Ford handle negative weights, nhưng nếu có negative cycle thì shortest path không tồn tại (có thể đi vòng mãi để giảm cost)
  • C đúng nhất: Hỏi clarifying question trước → nếu không có negative cycle → Bellman-Ford. Nếu có negative cycle → báo "shortest path undefined"
  • D sai: BFS chỉ cho shortest path trên unweighted graph

Bài học: Luôn hỏi clarifying questions. Interviewer hỏi "chắc chưa?" là đang cho bạn cơ hội sửa sai — hãy tận dụng.


🐛 Spot the Bug

Đoạn code binary search dưới đây có HAI bugs. Tìm cả hai:

python
def binary_search(arr, target):
    lo = 0
    hi = len(arr) - 1

    while lo < hi:                          # 🐛 Bug #1
        mid = (lo + hi) / 2                 # 🐛 Bug #2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1

    return -1
🔍 Phân tích bugs

Bug #1: while lo < hi — sai loop condition

Với inclusive bounds [lo, hi], khi lo == hi vẫn còn 1 element chưa check. Nếu target nằm ở vị trí đó → trả về -1 sai.

python
# ❌ Sai: bỏ sót element khi lo == hi
while lo < hi:

# ✅ Đúng: check cả khi lo == hi
while lo <= hi:

Bug #2: mid = (lo + hi) / 2 — hai lỗi trong một dòng

  1. Integer overflow: lo + hi có thể tràn số (trong C/C++/Java). Python không bị nhưng đây là bad habit
  2. Python-specific: / trả về float trong Python 3 → arr[mid] báo lỗi TypeError vì index phải là int
python
# ❌ Sai: overflow risk + float division
mid = (lo + hi) / 2

# ✅ Đúng: no overflow + integer division
mid = lo + (hi - lo) // 2

Code đã fix:

python
def binary_search(arr, target):
    lo = 0
    hi = len(arr) - 1

    while lo <= hi:                         # ✅ Fixed
        mid = lo + (hi - lo) // 2           # ✅ Fixed
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1

    return -1

🎭 Scenario: Mock Interview Replay

Bối cảnh: Ứng viên được hỏi bài Two Sum — tìm hai số trong array có tổng bằng target.

Ứng viên: "Dùng hai vòng for lồng nhau, check mọi cặp. O(n²)."

Interviewer: "Có cách nào nhanh hơn không?"

Ứng viên: Im lặng 3 phút...

Interviewer: "Nếu bạn đã thấy một số, bạn biết cần tìm số nào tiếp theo?"

Ứng viên: "À! Dùng HashMap — với mỗi số x, check xem target - x có trong map không. O(n)!"

Interviewer: "HashMap O(1) luôn phải không?"

🎯 Phân tích

Ứng viên rơi vào 3 traps:

  1. Trap #1 — Không communicate (General Trap #6): Im lặng 3 phút thay vì nói ra hướng suy nghĩ. Interviewer phải chủ động cho hint.

  2. Trap #2 — "HashMap always O(1)" (Data Structure Trap #4): Câu trả lời đúng là: "Average O(1) per lookup, nên overall là O(n) average. Worst-case nếu hash collision xấu thì O(n) per lookup → O(n²) worst-case, nhưng điều này cực hiếm với good hash function."

  3. Trap #3 — Không hỏi clarifying questions (General Trap #1): Trước khi code, nên hỏi:

    • Array có sorted không? (Nếu có → two pointers O(n) không cần extra space)
    • Có duplicate không? (Ảnh hưởng đến HashMap logic)
    • Có chính xác 1 solution hay nhiều? (Ảnh hưởng đến return type)

Bài học: Kỹ năng communicate và clarify quan trọng ngang bằng kỹ năng thuật toán.


🎭 Scenario 2: DP State Trap

Bối cảnh: Ứng viên được hỏi bài 0/1 Knapsack — chọn items có weight và value để maximize value trong capacity W.

Ứng viên: "Dùng DP. State là dp[i] = max value khi xét i items đầu tiên."

Interviewer: "State đó đủ chưa? Nếu bạn biết max value khi xét 3 items đầu — bạn có biết còn bao nhiêu capacity để xét item thứ 4 không?"

Ứng viên: Im lặng...

Interviewer: "State cần capture thêm information gì?"

🎯 Phân tích

Ứng viên mắc DP Trap #4 — Sai state definition:

  • dp[i] chỉ track "xét i items" nhưng không track remaining capacity
  • Không biết còn bao nhiêu capacity → không thể quyết định có pick item tiếp theo không
  • State đúng: dp[i][w] = max value khi xét i items đầu tiên VỚI capacity = w
  • Cần 2 dimensions: item index + remaining capacity

Quy tắc chọn state DP: State phải chứa đủ information để từ bất kỳ state nào, bạn có thể quyết định optimal action mà không cần biết gì thêm. Nếu thiếu info → thêm dimension.


Trap Prevention Checklist

Checklist này dùng trước và trong phỏng vấn. Print ra hoặc ghi nhớ:

Trước khi code (2-3 phút):

  • [ ] Hỏi clarifying questions: input range, edge cases, constraints
  • [ ] Nói approach + complexity trước khi viết code
  • [ ] Xác nhận: sorted? duplicates? negative numbers? empty input?

Trong khi code:

  • [ ] Binary search: dùng lo + (hi - lo) // 2, không dùng (lo + hi) / 2
  • [ ] Graph: viết visited = set() TRƯỚC khi viết BFS/DFS
  • [ ] DP: viết base cases TRƯỚC recurrence
  • [ ] Array size: n + 1 khi dp index chạy từ 0 đến n

Sau khi code (1-2 phút):

  • [ ] Dry-run qua ít nhất 1 example
  • [ ] Test edge cases: empty, single element, all same, extreme values
  • [ ] State time complexity + space complexity

📋 Cheat Sheet — Quick Reference

Bảng tóm tắt để review nhanh trước phỏng vấn:

TopicBẫy nguy hiểm nhấtMột câu để nhớ
Sorting"Quick Sort luôn O(n log n)""Average ≠ Worst. Sorted input + bad pivot = O(n²)"
Searchingmid = (lo + hi) / 2"Trừ trước, chia sau: lo + (hi - lo) / 2"
Data Structures"HashMap O(1) always""Average O(1), worst O(n). Nói amortized!"
GraphQuên visited set"Viết visited TRƯỚC khi viết BFS/DFS"
DPQuên base case"Base case trước, recurrence sau"
GeneralCode ngay không hỏi"5 phút clarify tiết kiệm 20 phút debug"


📍 Trang tiếp theo

Bạn đã nắm vững các bẫy phỏng vấn DSA. Tiếp theo, hãy xem cách kiến thức DSA kết nối trực tiếp với System Design — kỹ năng mà mọi senior engineer đều cần:

➡️ DSA → System Design Bridge