
Real Microsoft Interview Experience
4 Rounds: DSA, Machine Coding, UI Tech & System Design
Round 1: Data Structures & Algorithms (75 minutes)
2 medium difficulty array problems. Focus on optimal solution and edge cases.
1οΈβ£ Two Sum
Difficulty: Medium | Time: 25 minutes
Given an array of integers
nums and an integer target, return the indices of the two numbers that add up to the target. You may assume each input has exactly one solution, and you cannot use the same element twice.π Constraints:β’ -10βΉ β€ nums[i] β€ 10βΉ
β’ -10βΉ β€ target β€ 10βΉ
β’ Only one valid answer exists
β’ Cannot use same element twice
β’ Input: nums = [2, 7, 11, 15], target = 9 β Output: [0, 1] (2+7=9)
β’ Input: nums = [3, 2, 4], target = 6 β Output: [1, 2] (2+4=6)
β’ Input: nums = [3, 3], target = 6 β Output: [0, 1] (3+3=6)
β’ Input: nums = [1, 2, 3], target = 10 β Output: [] (no solution)
For each number, ask: "Have I seen the complement (target - current number) before?"
Example: target=9, current=7 β complement=2. If I've seen 2, return [index_of_2, current_index]
function twoSum(nums, target) {
// Use a Map to store value -> index for O(1) lookup
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
// If we've seen the complement before, return indices
if (map.has(complement)) {
return [map.get(complement), i];
}
// Store current number and its index
map.set(nums[i], i);
}
// No solution found
return [];
}
// Test cases
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 2, 4], 6)); // [1, 2]
console.log(twoSum([3, 3], 6)); // [0, 1]
console.log(twoSum([1, 2, 3], 10)); // []β Brute Force (Don't use):// O(nΒ²) time complexity - Inefficient!
function twoSumBrute(nums, target) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
return [];
}π Trace Through Example:nums = [2, 7, 11, 15], target = 9
Iteration 1: i=0, num=2
complement = 9 - 2 = 7
map.has(7)? NO
map = {2: 0}
Iteration 2: i=1, num=7
complement = 9 - 7 = 2
map.has(2)? YES! β return [0, 1] βπ‘ Key Insights:- HashMap Magic: Store valueβindex for instant lookups
- Complement Pattern: If target=9 and we see 7, we need 2
- One-Pass Solution: No need for nested loop
- Edge Cases: Duplicates (nums=[3,3], target=6), negatives, empty array
- Time: O(n) | Space: O(n)
2οΈβ£ Container With Most Water
Difficulty: Medium | Time: 30 minutes
Given an array
height where the integer at index i represents a vertical line at position i. Find two lines that together with the x-axis form a container such that the container holds the most water.π Constraints:β’ 0 β€ height[i] β€ 10β΄
β’ Time Complexity: Must be better than O(nΒ²)
β’ Return single integer (the maximum area)
height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
indices: 0 1 2 3 4 5 6 7 8
Visual:
8 8
| |
6 | | 6 | |
2 | | 2 | | 2
1 | | | 1 | | | 7
| | | | | | | | |
---+---+---+---+---+---+---+---+---
0 1 2 3 4 5 6 7 8
Best container: index 1 (height=8) to index 8 (height=7)
width = 8 - 1 = 7
height = min(8, 7) = 7
area = 7 Γ 7 = 49 βπ Why Two Pointers Works:function maxArea(height) {
let left = 0;
let right = height.length - 1;
let maxArea = 0;
while (left < right) {
// Calculate current area
const width = right - left;
const currentHeight = Math.min(height[left], height[right]);
const currentArea = width * currentHeight;
// Update max if current is larger
maxArea = Math.max(maxArea, currentArea);
// Move the pointer with smaller height
// (moving the taller one won't improve area anyway)
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
// Test cases
console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49
console.log(maxArea([1, 1])); // 1
console.log(maxArea([4, 3, 2, 1, 4])); // 16
console.log(maxArea([2, 3, 4, 5, 18, 17, 6])); // 17β Brute Force (Don't use):// O(nΒ²) time complexity
function maxAreaBrute(height) {
let maxArea = 0;
for (let i = 0; i < height.length; i++) {
for (let j = i + 1; j < height.length; j++) {
const area = (j - i) * Math.min(height[i], height[j]);
maxArea = Math.max(maxArea, area);
}
}
return maxArea;
}π Complete Trace:height = [1, 8, 6, 2, 5, 4, 8, 3, 7]
L=0(h=1) R=8(h=7)
Step 1: area = 8 Γ min(1,7) = 8
Move Lβ1 (1 < 7, move shorter)
L=1(h=8) R=8(h=7)
Step 2: area = 7 Γ min(8,7) = 49 β BEST!
Move Rβ7 (7 < 8, move shorter)
L=1(h=8) R=7(h=3)
Step 3: area = 6 Γ min(8,3) = 18
Move Rβ6 (3 < 8, move shorter)
... continue until L < R ...π‘ Key Insights:- Two Pointers Greedy Strategy: Start widest, move shorter pointer inward
- Why move shorter pointer? Only chance to get larger area since width decreases
- Proof: If we move the taller pointer, width β and min_height β€ (unchanged), so area β
- Guarantee: Best area must be considered (we try all possibilities for shorter line)
- Time: O(n) | Space: O(1)
π‘ DSA Interview Tips:
- Always clarify problem constraints (array size, value ranges, duplicates)
- Discuss multiple approaches: brute force β optimized
- Explain time/space complexity clearly
- Write clean code with meaningful variable names
- Test with edge cases: empty array, single element, duplicates
- Use visualization/drawings to explain logic
- Practice on LeetCode Medium problems regularly
Between you and this round stands one thing: seeing the optimal pattern. Most solve it, few solve it fast. Learn how our cohort members crack this β
Round 2: Machine Coding - Split Manager (90 minutes)
Build a splitwise-like app to manage shared expenses with friends.
βοΈ Problem: Split Manager Application
Time: 90 minutes
Build a Splitwise-like expense sharing application. The app helps groups of friends track who paid for expenses and who owes whom money. For example, if 3 friends go to dinner, one person pays $90, and they split it equally, the app calculates that the other 2 each owe $30.
Alice pays $90 for dinner. Split equally between Alice, Bob, and Charlie. Each person's share = $90 / 3 = $30. So Bob owes $30, Charlie owes $30.
- β Manage Friends: Add, view, and edit friends by name
- β Record Expenses:Add splits with description, amount, who paid, and who it's split between
- β View Data: Show all friends in a list and all expenses in a table
- β Calculate Balances: Show how much each person has spent (optional but impressive)
- β Edit Functionality: Edit existing friends and splits via modal popups
- β User-Friendly UI: Clean layout with modal forms for data entry
Forms: Modal popups with input fields for adding/editing data
Rendering: Map over friends and splits to display in lists/tables
Logic: Add items to arrays, update existing items by ID
The interviewer will evaluate you on:
- Requirements Understanding: Did you ask clarifying questions?
- Clean Code: Is the component structure clear and modular?
- State Management: Do you handle state correctly with hooks?
- Error Handling: What happens with invalid inputs?
- UI/UX: Is the interface intuitive and bug-free?
- Time Management: Did you finish and have time for bonus features?
function SplitManager() {
const [friends, setFriends] = useState([]);
const [splits, setSplits] = useState([]);
useEffect(() => {
setFriends([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
}, []);
const addFriend = (name) => {
setFriends([...friends, { id: Date.now(), name }]);
};
const addSplit = (desc, amount, paidBy, splitBetween) => {
setSplits([...splits, {
id: Date.now(),
desc, amount, paidBy, splitBetween
}]);
};
return (
<div>
<FriendsList friends={friends} onAdd={addFriend} />
<SplitsList splits={splits} friends={friends} />
</div>
);
}π‘ Machine Coding Best Practices:
- Ask clarifying questions about requirements first
- Build MVP (Minimum Viable Product) first
- Handle form validation and error states properly
- Use React hooks effectively (useState, useEffect)
- Manage component state clearly
- Think about edge cases (empty lists, invalid inputs)
- Write clean code with meaningful variable names
The code works, but is it beautiful? Clean architecture under pressure is the invisible skill Microsoft engineers judge. See how we teach this β
Round 3: UI Tech Deep Dive (60 minutes)
10 questions covering HTML, CSS, performance, and browser concepts.
1οΈβ£ Semantic HTML & Examples
β Good - Semantic:
<header>
<nav><a href="#">Home</a></nav>
</header>
<main>
<article>
<h1>Title</h1>
<p>Content</p>
</article>
<aside>Related</aside>
</main>
<footer></footer>Benefits: Better SEO, accessibility, readability2οΈβ£ <picture> vs <img> Tag
<picture>: Multiple images with art direction
<picture>
<source media="(max-width: 480px)" srcset="square.jpg">
<source media="(min-width: 481px)" srcset="wide.jpg">
<img src="fallback.jpg" alt="Description">
</picture>Use picture for: Different crops per device3οΈβ£ content-box vs border-box
border-box (β ): width includes padding/border
* { box-sizing: border-box; } /* Best practice */4οΈβ£ Loading 1000+ Images: Performance
β’ Lazy loading (loading="lazy")
β’ Intersection Observer API
β’ Virtual scrolling (react-window)
β’ Image optimization (WebP, compression)
β’ CDN distribution
// Lazy Load
<img src="image.jpg" loading="lazy" alt="Desc">
// Virtual Scroll
<FixedSizeList height={600} itemCount={1000}>5οΈβ£ Browser Storage: Which to Use?
sessionStorage: 5-10 MB, per tab, auto cleanup
Cookies: 4 KB, can set TTL
IndexedDB: GB+, async, great for large data
localStorage.setItem('theme', 'dark');
sessionStorage.setItem('draft', JSON.stringify(data));
document.cookie = "auth=token; max-age=3600";6οΈβ£ Web Vitals: LCP, FCP, CLS
LCP:Largest Contentful Paint < 2.5s
CLS:Cumulative Layout Shift < 0.1
Improve LCP:
<link rel="preload" as="image" href="hero.jpg">
<img src="hero.jpg" width="1200" height="600">7οΈβ£ Measure Performance After Code Changes
β’ Chrome DevTools β Performance tab
β’ Lighthouse (0-100 score)
β’ Web Vitals API
β’ performance.now()
// Before/After comparison
console.time('operation');
// ... code ...
console.timeEnd('operation');
// Web Vitals
import { getCLS, getFCP, getLCP } from 'web-vitals';
getCLS(console.log);8οΈβ£ Call Stack & Promises Output
console.log('1');
Promise.resolve().then(() => console.log('2'));
console.log('3');Output: 1, 3, 2Why: Sync first β Microtasks (Promises)
Async/Await Example:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');Output: 1, 4, 3, 29οΈβ£ Layout & Paint: When & How to Minimize
Paint: Triggered by color, background changes
Composite: Transforms (fastest)
// β Bad: Causes layout
for (let i = 0; i < 100; i++) {
el.style.width = (el.offsetWidth + 10) + 'px';
}
// β
Good: Use transform
el.style.transform = 'scale(1.1)';π Browser Rendering: HTML to Pixels
1. Parse HTML β DOM Tree
2. Load CSS β CSSOM Tree
3. Style Calculation β Styled DOM
4. Layout β Position each element
5. Paint β Draw pixels
6. Composite β Send to screen
Minimize: Reduce layout/paint; use transforms for animations
π‘ UI Tech Interview Tips:
- Show code examples for HTML/CSS questions
- Understand the "why" behind performance
- Use DevTools to demonstrate knowledge
- Connect concepts: HTML β CSS β JS β Performance
- Mention real-world scenarios
- Show accessibility awareness
Round 4: System Design - Autocomplete System (60 minutes)
Design a type-ahead search with local caching. Includes design thinking and implementation.
π Problem: Autocomplete/Type-Ahead Search
Time: 60 minutes | Focus: Design + Code
Design a type-ahead search system (like Google's search suggestions). As the user types, the app should instantly show matching suggestions from a large dataset of 100K+ items.
Google autocomplete suggests "javascript", "jest", "json" when you type "js". It needs to be instant (< 50ms), handle millions of queries, and work offline with cached data.
β Large Datasets: Handle 100K+ suggestions efficiently
β Local Caching:Minimize API calls (cache hit rate > 80%)
β Offline Support: Works with previously cached data
β Smart Matching:Prefix matching (user types "jav" β show "javascript")
β Memory Efficient:Don't cache unlimited queries
- User types "r" β App queries cache β Shows ["react", "redux", "ruby"]
- User types "ea" (full: "rea") β Query "rea" not in cache β Fetch from API
- API returns ["react", "read"] β Cache this query β Display to user
- User types "ct" (full: "react") β Query "react" found in cache β Instant display
| Constraint | Detail |
|---|---|
| Latency | User expects < 100ms response |
| Memory | Browser localStorage/IndexedDB limited (5-10 MB) |
| Network | Minimize API calls to reduce server load |
| Relevance | Show most relevant results (popularity, recency) |
| Approach | Time/Query | Space | Pros | Cons |
|---|---|---|---|---|
| Simple Map + Filter | O(n) | O(n) | Easy to code | Slow for 100K items |
| Trie Data Structure | O(k) k=prefix len | O(n*m) | Fast prefix search | Complex implementation |
| Trie + LRU Cache | O(1) cached | O(cache_size) | β Production ready | Most complex |
Dataset: 100,000 search suggestions (average 10 chars each)
Approach 1 (Simple Map):
User types "je" β O(100K) filtering β 45-50ms delay β
Approach 2 (Trie):
User types "je" β O(2) traversal β 2-3ms response β
Approach 3 (Trie + LRU Cache):
First "je" query β 2-3ms (Trie)
Second "je" query β 0.1ms (Cache hit) β‘ 50x faster!Design Approach Evolution:Store suggestions in a Map, filter on input
Time: O(n) per query | Space: O(n)
Problem: Slow for large datasets
How it works: For every keystroke, iterate through all 100K items and filter by prefix
When to use:Small datasets only (< 1000 items)
Store words in Trie, traverse branches for prefix match
Time: O(k) where k = length of prefix | Space: O(n*m)
Benefit: Much faster prefix matching
How it works:Build a tree structure where each node represents a character. To find "react", traverse 5 nodes instead of searching 100K items
When to use: Large datasets with prefix matching (most cases)
Cache recent queries with LRU eviction
Time: O(k) first query, O(1) cached | Space: O(cache_size)
Benefit: Fast repeated queries, bounded memory
suggestions = ["javascript", "jest", "json", "python", "java", ...100K items]
Step 1: User types "j"
Filter: suggestions.filter(s => s.startsWith("j"))
Check 100,000 items β Find ["javascript", "jest", "json", "java"]
Time: 5-10ms
Step 2: User types "e" (full: "je")
Filter: suggestions.filter(s => s.startsWith("je"))
Check 100,000 items again β Find ["jest", "javascript"]
Time: 5-10ms
Problem: Every keystroke requires O(n) filtering! β// Approach 1: Map + Filter (Simple but slow)
class SimpleAutocomplete {
constructor(suggestions) {
this.suggestions = suggestions;
this.cache = new Map();
}
search(query) {
if (this.cache.has(query)) {
return this.cache.get(query);
}
// O(n) filter - slow for large datasets
const results = this.suggestions.filter(item =>
item.toLowerCase().startsWith(query.toLowerCase())
).slice(0, 10);
this.cache.set(query, results);
return results;
}
}Advanced: Trie + LRU Cacheπ³ Trie Structure Example:Build Trie from ["react", "redux", "ruby", "python"]
Root
/ | | \
r p ...
/ \
e y
| |
a t
| |
c h
| |
t o
(END) |
| n
["react"] (END)
["python"]
User types "re" β Traverse: root β r β e
Suggestions at node "e" = ["react", "redux"] β
Time: O(2) instead of O(n)!// Approach 3: Trie for fast prefix search
class TrieNode {
constructor() {
this.children = {};
this.isEnd = false;
this.suggestions = [];
}
}
class AutocompleteWithTrie {
constructor(suggestions) {
this.trie = new TrieNode();
this.queryCache = new Map(); // LRU cache
this.maxCacheSize = 100;
// Build Trie - O(n*m) where n=words, m=avg length
suggestions.forEach(word => this.insertWord(word));
}
insertWord(word) {
let node = this.trie;
for (let char of word) {
if (!node.children[char]) {
node.children[char] = new TrieNode();
}
node = node.children[char];
node.suggestions.push(word);
}
node.isEnd = true;
}
search(query) {
// Check LRU cache first
if (this.queryCache.has(query)) {
return this.queryCache.get(query);
}
let node = this.trie;
for (let char of query) {
if (!node.children[char]) {
return [];
}
node = node.children[char];
}
const results = node.suggestions.slice(0, 10);
// Add to cache with LRU eviction
this.queryCache.set(query, results);
if (this.queryCache.size > this.maxCacheSize) {
const firstKey = this.queryCache.keys().next().value;
this.queryCache.delete(firstKey);
}
return results;
}
}Local Caching Strategies:// Map for session-specific queries
const queryCache = new Map();
// Store recent searches
queryCache.set('react hooks', [...results]);
// Check before API call
if (queryCache.has(query)) {
return queryCache.get(query); // Instant!
}When to Use: Repeated queries in same sessionPros: Instant response | Cons: Lost on refresh
2. localStorage (Persistent)
// Persist across sessions
const cached = localStorage.getItem('autocomplete_cache');
let cache = cached ? JSON.parse(cached) : {};
// Save on new query
cache[query] = results;
localStorage.setItem('autocomplete_cache',
JSON.stringify(cache));
// Check later
const results = cache[query];When to Use: User's search historyPros: Persists across sessions | Cons: 5-10MB limit, sync only
3. IndexedDB (Large Datasets)
// Async, can store 100s MB
const db = await openDB('autocomplete');
// Store suggestions
await db.put('suggestions', {
query: 'react',
results: [...],
timestamp: Date.now()
});
// Retrieve
const cached = await db.get('suggestions', 'react');When to Use: Offline-first apps, large suggestion listsPros: Large storage, async | Cons: Complex, slower than Map
async search(query) {
// 1. Check in-memory cache (fastest)
if (this.memoryCache.has(query)) {
return this.memoryCache.get(query);
}
// 2. Check localStorage (medium)
const storedResults = localStorage.getItem(`ac_${query}`);
if (storedResults) {
this.memoryCache.set(query, JSON.parse(storedResults));
return JSON.parse(storedResults);
}
// 3. Check IndexedDB (slow but has more data)
const dbResults = await db.get('queries', query);
if (dbResults) {
this.memoryCache.set(query, dbResults);
return dbResults;
}
// 4. Fetch from API (slowest)
const results = await fetch(`/api/search?q=${query}`);
// Cache at all levels
this.memoryCache.set(query, results);
localStorage.setItem(`ac_${query}`, JSON.stringify(results));
await db.put('queries', { id: query, results });
return results;
}Optimization Techniques:2. Request Coalescing - If two requests for same query pending, merge into one
3. Prefix Caching- Cache results for 'r', reuse for 'react'
4. TTL for Cache - Expire cached results after 24 hours
5. LRU Eviction - Keep only most recent 100 queries in memory
- "How many suggestions should we show? 10? 100?" β Answer: ~10 suggestions
- "How many total items to search?" β Answer: 100K+ packages
- "What's acceptable latency?" β Answer: < 50ms for user to feel instant
- "Do we need offline support?" β Answer: Nice to have with cached data
- "Should we prioritize popular packages first?" β Answer: Yes, rank by downloads
- Frontend: React component with input, dropdown list, caching logic
- Browser Storage: Map (session) β localStorage (persistent) β IndexedDB (large)
- Backend API:GET /api/search?q=react β Returns ["react", "react-dom", ...]
- Data Structure: Start with Array, optimize to Trie for large datasets
- Simple Approach:"Let me start with a simple solution using a Map"
- Store all 100K suggestions in memory
- Filter on each keystroke: suggestions.filter(s => s.startsWith(query))
- Cache results in a Map to avoid re-filtering
- Optimization:"This might be slow for 100K items. Let me optimize with a Trie"
- Build Trie once (one-time cost)
- Traverse 5 chars instead of filtering 100K items
- Much faster prefix matching
- Production Ready:"Add LRU caching for repeated queries"
- Cache recent 100 queries
- Evict oldest when full
- Skip Trie lookup for cached queries
| Decision | Pros | Cons |
|---|---|---|
| Trie vs Map | Trie is O(k) vs Map O(n) | Trie is complex to implement |
| localStorage vs IndexedDB | localStorage simpler, IndexedDB larger | IndexedDB async, localStorage sync |
| Cache Size | Larger = more hits | Larger = more memory usage |
- What if API is down? β Use cached data from localStorage/IndexedDB
- What if user searches for special characters? β Escape and handle properly
- What if cache becomes too large? β Implement LRU eviction policy
- What if user types very fast (rapid keystrokes)? β Debounce API calls (300ms)
- What about unicode/emojis? β Trie supports any character
0-5 min: Clarify requirements
5-10 min: Draw architecture diagram
10-35 min: Code up solution (simple β optimized)
35-50 min: Discuss trade-offs and improvements
50-60 min: Handle edge cases, answer questionsπ‘ System Design Interview Tips:
- Clarify requirements upfront (scale, latency, offline support)
- Start simple (Map), then optimize (Trie + Cache)
- Explain trade-offs: Speed vs Memory vs Complexity
- Show understanding of caching hierarchy
- Mention when to use each storage option
- Think about edge cases: Empty query, special chars, Unicode
- Discuss failure scenarios: API down, cache full
Between you and Microsoft's system design lies caching patterns and trade-off thinking. They always ask about it. Most fumble. Some shine. Learn to shine with us β
You Now Know What Microsoft Expects π
From data structures to system design masteryβthis is the complete Microsoft interview roadmap. But knowing what to expect and being able to deliver under pressure are two different things.
Our cohort has helped a few developers crack interviews at Microsoft, Atlassian, Uber, and Amazon. They didn't just learn the conceptsβthey learned to think like a Microsoft engineer.
What our cohort members get:
- β Structured 8-week curriculum covering all 4 rounds
- β 50+ mock interviews (real-time, recorded, feedback)
- β 1-on-1 mentoring with engineers from Microsoft, Atlassian, etc.
- β Private community (25 dedicated developers)
- β Lifetime access to recordings & materials
- β 100% money-back if you don't get offers
Cohort 3 starts October 2026. Limited to 25 seats. Join the waitlist now.
Ready for Microsoft?
Join our cohort for structured prep, mock interviews, and personalized feedback.
Join Next Cohort