軟體工程師職涯升級計畫啟動!立即預約職涯諮詢、履歷健檢或模擬面試👈,為您的加薪做好準備!
🔹 第一部分:技術面試
題目:Remove Duplicates from Sorted Array(移除排序陣列中的重複元素)
在技術面試中,我遇到的題目是經典的:
在一個已排序的陣列中移除重複元素,並以 in-place 的方式回傳去重後的長度。
Two Pointers(slow / fast)方式
- fast pointer 負責尋找下一個不同的值
- slow pointer 負責覆蓋陣列,確保 in-place 操作
- 不需要額外的記憶體
- 時間複雜度:O(n)
- 空間複雜度:O(1)
function removeDuplicates(nums) {面試官關注內容包括:
if (nums.length === 0) return 0;
let slow = 0;
for (let fast = 1; fast < nums.length; fast++) {
if (nums[fast] !== nums[slow]) {
slow++; // 移動 slow,準備填入新數字
nums[slow] = nums[fast];
}
}
// slow 指向最後一個有效元素,因此長度是 slow + 1
return slow + 1;
}
// 使用範例
const arr = [0, 0, 1, 1, 2, 2, 3];
const len = removeDuplicates(arr);
console.log(len); // e.g. 4
console.log(arr.slice(0, len)); // [0, 1, 2, 3]
- 是否理解 in-place 的意義
- 如何解釋複雜度
- 是否能清楚 verbalize 解題過程
這一題是我在 Microsoft 面試中唯一被問到的演算法題,也是評估基礎邏輯與程式能力的重要環節。
🔹 第二部分:文化面試
核心重點:從小任務開始累積信任
文化面試中,我收到面試官非常有價值的一段回饋:
在 Microsoft,要有耐心,先把小任務做好,慢慢累積信用,團隊自然會把更大的事情交給你。
這段話反映出 Microsoft 非常重視:
- 穩定輸出(consistent delivery)
- 團隊信任的建立(trust-building)
- 願意承擔責任,但不躁進
- 以成果證明能力,而不是一次追求做最大、最亮眼的事情
這部分讓我對 Microsoft 的團隊文化有更清楚的理解:
技術強固然重要,但能否可靠、可預期地完成任務更關鍵。
🔹 第三部分:系統面試補充
物件導向設計(以自動售貨機為例)
雖然我沒有分享過詳細的題目內容,但我提過:
系統面試會注重物件導向設計(OOP),並以「自動販賣機」為例。
以自動販賣機作為系統面試的討論題目時,通常會讓你展示:
物件導向思維(OOP Concepts)
- 如何做類別拆分
- 如何設計責任分配(Single Responsibility)
- 使用哪些物件與方法
- 如何處理擴展(例如新增商品或支付方式)
- 是否能保持介面清晰且易維護
用自動販賣機做示例常會涉及:
VendingMachineItemInventoryPaymentProcessorState(等待投幣、選擇商品、出貨等)
面試官主要看的是你是否能用物件導向的方式思考系統邏輯,而不是單純寫程式碼。
📦 1. Item(商品)
class Item {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
📦 2. Inventory(庫存管理)
class Inventory {
constructor() {
this.items = new Map(); // name -> { item, quantity }
}
addItem(item, quantity) {
this.items.set(item.name, { item, quantity });
}
hasItem(name) {
const data = this.items.get(name);
return data && data.quantity > 0;
}
getItem(name) {
return this.items.get(name)?.item;
}
reduceItem(name) {
if (this.hasItem(name)) {
this.items.get(name).quantity--;
}
}
}
🧠 3. Vending Machine 主邏輯(簡易版本)
class VendingMachine {
constructor() {
this.inventory = new Inventory();
this.balance = 0; // 使用者投入金額
}
insertCoin(amount) {
this.balance += amount;
console.log(`Inserted €${amount}. Current: €${this.balance}`);
}
addItem(item, qty) {
this.inventory.addItem(item, qty);
}
selectItem(name) {
if (!this.inventory.hasItem(name)) {
console.log("Item out of stock.");
return;
}
const item = this.inventory.getItem(name);
if (this.balance < item.price) {
console.log("Not enough balance.");
return;
}
// 出貨
this.inventory.reduceItem(name);
this.balance -= item.price;
console.log(`Dispensing ${item.name}. Change: €${this.balance}`);
this.balance = 0;
}
}
🧪 使用示例
const vm = new VendingMachine();
vm.addItem(new Item("Coke", 2), 5);
vm.addItem(new Item("Water", 1), 3);
vm.insertCoin(2);
vm.selectItem("Coke");
// Output:
// Inserted €2. Current: €2
// Dispensing Coke. Change: €0
⭐ 面試時可以表達的 OOP 重點
- Single Responsibility:
- Item 負責商品資料
- Inventory 負責庫存
- VendingMachine 負責金額與流程
- Encapsulation:
- 庫存、餘額對外不可直接修改
- Extendability:
可未來加入: - State pattern(Idle / Waiting / Dispensing)
- PaymentProcessor:支援卡片、QR Code
- Logging:記錄銷售
- Clear Interfaces:
讓使用者只需要呼叫:insertCoin -> selectItem -> dispense
🧱 SOLID 原則在自動販賣機(Vending Machine)系統中的應用說明
以下是面試時非常加分的講法,因為 Microsoft(與 Google、Meta 等)都非常重視工程可維護性與抽象設計能力。
✔ S — Single Responsibility Principle(單一職責原則)
每個類別應該只有一個明確的責任。
在你的設計中:
Item只負責描述商品(名稱、價格)Inventory只負責管理庫存數量與商品存取VendingMachine只負責金額處理、選擇商品、出貨流程
這讓每個類別更容易維護,也能獨立測試。
✔ O — Open/Closed Principle(開放封閉原則)
系統對「擴充」開放,但對「修改原有程式碼」封閉。
在販賣機的例子中:
- 若未來新增 Apple Pay、Google Pay、信用卡,可以獨立建立
CreditCardPayment,QRCodePayment,NfcPayment
而不需修改VendingMachine本體邏輯。 - 若要新增商品,只需要呼叫:
vm.addItem(new Item("Tea", 2), 10);
不需要修改核心類別。
這代表你的設計易於擴充而不破壞既有程式碼。
✔ L — Liskov Substitution Principle(里氏替換原則)
子類別必須能替換掉父類別,不造成程式行為問題。
如果你未來加入:
class PaymentProcessor { pay(amount) {} }
class CardPayment extends PaymentProcessor {}
class QRPayment extends PaymentProcessor {}
那麼:
function checkout(paymentProcessor) {
paymentProcessor.pay(5);
}
不論傳入 CardPayment 或 QRPayment 都應該正常工作。
這確保系統能保持一致行為,不因替換子類別而壞掉。
✔ I — Interface Segregation Principle(介面分離原則)
不應強迫類別實作它不需要的介面。
例如,不需要讓:
ItemInventory
也實作「付款」相關方法。
而應保持清楚區隔:
IPaymentProcessorIInventoryManagerIVendingState
讓每個介面都保持專注,避免單一巨大的 interface(這是許多工程師常犯的錯)。
✔ D — Dependency Inversion Principle(依賴反轉原則)
高層模組不應依賴低層模組,而應依賴「抽象」。
你的 VendingMachine 可寫成:
class VendingMachine {
constructor(paymentProcessor) {
this.paymentProcessor = paymentProcessor; // 依賴抽象(介面)
}
checkout(price) {
this.paymentProcessor.pay(price);
}
}
現在:
const vm = new VendingMachine(new QRPayment());
或:
const vm = new VendingMachine(new CardPayment());
都可以 不用修改 VendingMachine 本體。
這使得:
- 程式更容易測試(可以傳入 MockPayment)
- 更容易擴充(新增付款方式不用動核心程式碼)
⭐ 完整總結
我的 Vending Machine 設計遵守了 SOLID 原則:
- 透過 SRP,將商品、庫存、付款邏輯清楚分層;
- 使用 OCP,讓新增商品與付款方式不需要改動核心 code;
- 透過 LSP 與抽象化付款接口,讓所有 payment class 都能互換;
- 使用 ISP 避免巨型介面,保持責任明確;
- 透過 DIP,機器依賴抽象而不是具體實作,增加可測試性與可維護性。









