4 Rounds: String Algorithms, Async JavaScript, System Basics & HR
Staff Software Engineer @ Walmart Global Tech
Successfully guided 100+ developers to top company offers (Microsoft, Uber, Adobe, Atlassian)
Medium difficulty string manipulation problem. Focus on optimal solution and handling edge cases.
Difficulty: Medium | Time: 50 minutes | Topic: String/HashMap
s and a list of words words, determine if s can be formed by concatenating words from the list. Each word in the list can be used at most once. The order of words in the list is not important, but each word can only be used as many times as it appears in the list.
π Constraints:
truefalse (missing "qrst")false (only 1 "ab" available, need 2)truefunction canFormString(s, words) {
// Count frequency of each word
const wordCount = {};
for (const word of words) {
wordCount[word] = (wordCount[word] || 0) + 1;
}
// Backtracking function
function backtrack(index) {
// If we've matched entire string, success!
if (index === s.length) {
return true;
}
// Try each available word
for (const word in wordCount) {
if (wordCount[word] > 0) {
// Check if current position can start with this word
if (s.substring(index, index + word.length) === word) {
// Use this word
wordCount[word]--;
// Recursively try to match rest of string
if (backtrack(index + word.length)) {
return true;
}
// Backtrack - restore the word
wordCount[word]++;
}
}
}
return false;
}
return backtrack(0);
}
// Test cases
console.log(canFormString("abcdefghijklmnop",
["ab","cd","ef","gh","ij","kl","mn","op"])); // true
console.log(canFormString("abcdefghijklmnopqrst",
["ab","cd","ef","gh","ij","kl","mn","op"])); // false
console.log(canFormString("abab", ["ab"])); // false
console.log(canFormString("aaab", ["aa","ab"])); // true
β‘ Trace Through Example:
s = "abab", words = ["ab", "ab"]
wordCount = {ab: 2}
backtrack(0):
- Try word "ab"
- s[0:2] = "ab" matches "ab" β
- wordCount[ab] = 1
- backtrack(2):
- Try word "ab"
- s[2:4] = "ab" matches "ab" β
- wordCount[ab] = 0
- backtrack(4):
- index === s.length (4 === 4) β return true β
Result: true
β οΈ Common Mistakes:
String manipulation is foundational. What Adobe really tests is problem decomposition under pressureβcan you think clearly when syntax isn't the bottleneck? That's the real skill. Learn to solve like the best β
Build a system to manage and execute async tasks sequentially with callbacks.
Difficulty: Hard | Time: 80 minutes | Topic: Async/Promises/Callbacks
AsyncTaskQueue class that:
onAdd(task) - callback when a task is addedonCompute(result) - callback when a task completes with resultonComplete() - callback when ALL tasks are finishedclass AsyncTaskQueue {
constructor(tasks = []) {
this.tasks = [...tasks]; // Queue of tasks to execute
this.isRunning = false; // Flag to track if queue is processing
this.currentTaskIndex = 0; // Track which task we're on
this.results = []; // Store results of completed tasks
// Callbacks
this.onAddCallback = null;
this.onComputeCallback = null;
this.onCompleteCallback = null;
}
// Register callback for when task is added
onAdd(callback) {
this.onAddCallback = callback;
}
// Register callback for when task completes
onCompute(callback) {
this.onComputeCallback = callback;
}
// Register callback for when all tasks are done
onComplete(callback) {
this.onCompleteCallback = callback;
}
// Add a new task to the queue
addTask(task) {
this.tasks.push(task);
// Trigger onAdd callback
if (this.onAddCallback) {
this.onAddCallback(task);
}
// If not currently running, start processing
if (!this.isRunning) {
this.process();
}
}
// Process tasks sequentially
async process() {
if (this.isRunning) return; // Prevent multiple simultaneous processes
this.isRunning = true;
while (this.currentTaskIndex < this.tasks.length) {
try {
const task = this.tasks[this.currentTaskIndex];
// Execute the async task
const result = await Promise.resolve(task());
// Store result
this.results.push(result);
// Trigger onCompute callback with result
if (this.onComputeCallback) {
this.onComputeCallback(result);
}
this.currentTaskIndex++;
} catch (error) {
console.error(`Task ${this.currentTaskIndex} failed:`, error);
// Optionally continue or break on error
this.currentTaskIndex++;
}
}
// All tasks completed
this.isRunning = false;
// Trigger onComplete callback
if (this.onCompleteCallback) {
this.onCompleteCallback(this.results);
}
}
// Get all results
getResults() {
return this.results;
}
}
// Example Usage:
const queue = new AsyncTaskQueue();
// Register callbacks
queue.onAdd((task) => {
console.log("π Task added to queue");
});
queue.onCompute((result) => {
console.log(`β
Task computed with result:`, result);
});
queue.onComplete((results) => {
console.log("π All tasks completed!", results);
});
// Add tasks
queue.addTask(() =>
new Promise(resolve => {
setTimeout(() => resolve("Task 1 done"), 1000);
})
);
queue.addTask(() =>
new Promise(resolve => {
setTimeout(() => resolve("Task 2 done"), 500);
})
);
queue.addTask(() => {
return "Task 3 done (synchronous)";
});
π Real-World Example - Processing API Requests:
const apiQueue = new AsyncTaskQueue();
// Setup callbacks
apiQueue.onCompute((data) => {
console.log("API Response:", data);
});
apiQueue.onComplete((allResults) => {
console.log("All API calls completed:", allResults);
});
// Add API calls as tasks
apiQueue.addTask(() =>
fetch('https://api.example.com/users/1').then(r => r.json())
);
apiQueue.addTask(() =>
fetch('https://api.example.com/posts/1').then(r => r.json())
);
apiQueue.addTask(() =>
fetch('https://api.example.com/comments/1').then(r => r.json())
);
π‘ Interview Approach:
Async code separates the safe coders from the ones who understand systems. Adobe isn't looking for someone who knows async/await syntaxβthey want engineers who can reason about concurrency and callbacks under real-world constraints. Build systems with confidence β
Core concepts testing and practical React patterns. Expect deep questions on rendering, hooks, and optimization.
Difficulty: Medium | Time: 20 minutes
// β WRONG: Using array index as key
function RecursiveListBad({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>
{item.name}
{item.children && item.children.length > 0 && (
<RecursiveListBad items={item.children} />
)}
</li>
))}
</ul>
);
}
// Problem: If you reorder items or filter them, React gets confused
// It uses index 0, 1, 2... which doesn't map to actual items
// β
CORRECT: Using unique identifier as key
function RecursiveListGood({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name}
{item.children && item.children.length > 0 && (
<RecursiveListGood items={item.children} />
)}
</li>
))}
</ul>
);
}
// Real-world example with dynamic list
const TreeNode = ({ node }) => {
const [expanded, setExpanded] = useState(false);
return (
<div style={{ marginLeft: '20px' }}>
<div onClick={() => setExpanded(!expanded)}>
{expanded ? 'βΌ' : 'βΆ'} {node.name}
</div>
{expanded && node.children && (
<div>
{node.children.map(child => (
<TreeNode key={child.id} node={child} />
))}
</div>
)}
</div>
);
};
// Usage
const fileTree = {
id: '1',
name: 'root',
children: [
{ id: '2', name: 'folder1', children: [
{ id: '3', name: 'file1.js' }
]}
]
};
export default function FileExplorer() {
return <TreeNode node={fileTree} />;
}
π Key Insights:
Difficulty: Medium | Time: 20 minutes
// Pattern 1: Run once on mount only
useEffect(() => {
console.log('Component mounted');
// Perfect for: API calls, subscriptions, event listeners
}, []);
// Cleanup runs on unmount
// Pattern 2: Run when dependency changes
useEffect(() => {
console.log('UserId changed:', userId);
// Perfect for: Reacting to prop/state changes
}, [userId]);
// Cleanup runs before effect runs again + on unmount
// Pattern 3: Run after EVERY render (DANGEROUS!)
useEffect(() => {
console.log('Runs after every render!');
// Avoid this! Can cause infinite loops and performance issues
});
// β MEMORY LEAK: Event listener not cleaned up
function ChatWindow({ userId }) {
useEffect(() => {
const handleNewMessage = (message) => {
console.log('New message:', message);
};
// Subscribe to WebSocket
socket.on('message', handleNewMessage);
// Missing cleanup! listener stays registered even after unmount
}, [userId]);
return <div>Chat</div>;
}
// β
CORRECT: Cleanup function unsubscribes
function ChatWindow({ userId }) {
useEffect(() => {
const handleNewMessage = (message) => {
console.log('New message:', message);
};
// Subscribe
socket.on('message', handleNewMessage);
// Cleanup function removes listener
return () => {
socket.off('message', handleNewMessage);
};
}, [userId]);
return <div>Chat</div>;
}
// Real-world example: API call with proper cleanup
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
const data = await fetch(`/api/users/${userId}`);
const user = await data.json();
// Only update state if component is still mounted
if (isMounted) {
setUser(user);
setLoading(false);
}
};
fetchUser();
// Cleanup: mark component as unmounted
return () => {
isMounted = false;
};
}, [userId]);
return loading ? <div>Loading...</div> : <div>{user.name}</div>;
}
π Common Mistakes:
Difficulty: Hard | Time: 20 minutes
// β CAUSES INFINITE LOOP
function BadExample() {
const [count, setCount] = useState(0);
// Problem: onUserAction is recreated every render
// Even though the function body is the same, it's a new reference
const onUserAction = () => setCount(count + 1);
useEffect(() => {
// Problem: onUserAction is in dependency array
// It changes every render β effect runs every render
// Effect creates new onUserAction β infinite loop!
element.addEventListener('click', onUserAction);
return () => element.removeEventListener('click', onUserAction);
}, [onUserAction]); // β Bad dependency
return <button>Count: {count}</button>;
}
β
Solutions:
// Solution 1: Use useCallback to memoize the function
function GoodExample1() {
const [count, setCount] = useState(0);
const onUserAction = useCallback(() => {
setCount(c => c + 1); // Use functional update
}, []); // No dependencies = function never changes
useEffect(() => {
element.addEventListener('click', onUserAction);
return () => element.removeEventListener('click', onUserAction);
}, [onUserAction]); // Safe dependency
return <button>Count: {count}</button>;
}
// Solution 2: Use functional setState (recommended)
function GoodExample2() {
const [count, setCount] = useState(0);
useEffect(() => {
const onUserAction = () => setCount(c => c + 1);
element.addEventListener('click', onUserAction);
return () => element.removeEventListener('click', onUserAction);
}, []); // No dependencies needed!
return <button>Count: {count}</button>;
}
π‘ Why Re-renders Happen:
React is a tool, JavaScript is the craft. What Adobe values is engineers who deeply understand why React exists, not just how to use itβthe patterns, the philosophy, the performance thinking. Master the philosophy β
Deep dive into system-level concepts and their impact on application performance.
Difficulty: Hard | Time: 40 minutes
// β CACHE-UNFRIENDLY (Row-major vs Column-major mismatch)
function sumMatrixUnfriendly(matrix) {
let sum = 0;
const rows = matrix.length;
const cols = matrix[0].length;
// Accessing column by column in row-major stored matrix
for (let col = 0; col < cols; col++) {
for (let row = 0; row < rows; row++) {
sum += matrix[row][col]; // Huge cache misses!
}
}
return sum;
}
// β
CACHE-FRIENDLY (Sequential memory access)
function sumMatrixFriendly(matrix) {
let sum = 0;
const rows = matrix.length;
const cols = matrix[0].length;
// Accessing row by row (sequential memory access)
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
sum += matrix[row][col]; // Cache hits!
}
}
return sum;
}
// Performance Test
const matrix = Array(10000).fill(0).map(() =>
Array(10000).fill(Math.random())
);
console.time("Unfriendly");
sumMatrixUnfriendly(matrix);
console.timeEnd("Unfriendly"); // ~500ms
console.time("Friendly");
sumMatrixFriendly(matrix);
console.timeEnd("Friendly"); // ~100ms (5x faster!)
π― Key Concepts for Frontend Engineers:
// DOM Operations - Cache misses with scattered DOM nodes
// β Unfriendly: Touching scattered DOM nodes
for (let i = 0; i < 1000; i++) {
document.getElementById(`item-${i}`).style.color = 'red';
// Each query walks the DOM tree (cache unfriendly)
}
// β
Friendly: Batch DOM reads, then batch DOM writes
const nodes = [];
for (let i = 0; i < 1000; i++) {
nodes.push(document.getElementById(`item-${i}`));
}
for (const node of nodes) {
node.style.color = 'red';
}
// or better yet: Use DocumentFragment or classList batch operations
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const item = document.getElementById(`item-${i}`);
item.classList.add('highlight');
}
// Triggers single reflow instead of 1000
π Real-World Optimization:
System thinking isn't about memorizing architecture patterns. It's about understanding trade-offsβlatency vs throughput, consistency vs availability, simplicity vs scalability. That's where Adobe separates the engineers who code from those who architect. Think like an architect β
General HR conversation about experience, growth, and fit.
From string algorithms to system caching strategiesβthis is the complete Adobe interview roadmap. But knowing what to expect and being able to deliver under pressure are two different things.
Our cohort has helped developers crack interviews at Adobe, Microsoft, Uber, Atlassian, and Amazon. They didn't just learn the conceptsβthey learned to think like an Adobe engineer.
What our cohort members get:
Cohort 3 starts October 2026. Limited to 25 seats. Join the waitlist now.