Machine Coding Interview Question
Build a Multi-Select Dropdown with Search (Tag Input)
This is VERY frequently asked in frontend interviews.
📌 Problem Statement
Build a Multi-Select Dropdown component like this:
[ React × ] [ JavaScript × ] [ + Add ]
Clicking "Add" opens a dropdown with searchable options.
📦 Data
const options = [
"React",
"JavaScript",
"TypeScript",
"Node.js",
"Next.js",
"HTML",
"CSS"
];
✅ Requirements
1️⃣ Multi Select
User can select multiple options
Selected items appear as tags
[ React × ] [ CSS × ]
2️⃣ Remove Tag
Each tag has ❌
Clicking removes it
3️⃣ Dropdown Toggle
Clicking input or button opens dropdown
Clicking outside closes it
4️⃣ Search Filter
Typing filters options:
Input: "re"
→ React
5️⃣ Prevent Duplicate Selection
Already selected items should:
Not appear in dropdown OR
Be disabled
6️⃣ Keyboard Support (Important)
Enter → select highlighted item
Backspace → remove last tag (if input empty)
⚠️ Constraints (Interview Critical)
❌ No libraries
❌ No mutation of data
✅ Proper state modeling
✅ Event handling (click outside!)
❌ Avoid unnecessary re-renders
🎯 Edge Cases Interviewers Test
Case Expected Behavior
Rapid typing Filter works smoothly
Duplicate select Prevented
Backspace Removes last tag
Click outside Closes dropdown
No results Show "No options"
import { useEffect, useMemo, useRef, useState } from "react";
const options = [
"React",
"JavaScript",
"TypeScript",
"Node.js",
"Next.js",
"HTML",
"CSS"
];
export default function MultiSelect() {
const [query, setQuery] = useState("");
const [selected, setSelected] = useState([]);
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
// click outside
useEffect(() => {
function handleClickOutside(e) {
if (ref.current && !ref.current.contains(e.target)) {
setIsOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () =>
document.removeEventListener("mousedown", handleClickOutside);
}, []);
const filtered = useMemo(() => {
return options
.filter((opt) => !selected.includes(opt))
.filter((opt) =>
opt.toLowerCase().includes(query.toLowerCase())
);
}, [query, selected]);
const handleSelect = (item) => {
setSelected((prev) => [...prev, item]);
setQuery("");
};
const handleRemove = (item) => {
setSelected((prev) => prev.filter((i) => i !== item));
};
return (
<div ref={ref} style={{ width: 300 }}>
<div
style={{ border: "1px solid black", padding: 8 }}
onClick={() => setIsOpen(true)}
>
{selected.map((item) => (
<span key={item} style={{ marginRight: 4 }}>
{item}
<button onClick={() => handleRemove(item)}>❌</button>
</span>
))}
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
</div>
{isOpen && (
<ul style={{ border: "1px solid gray", marginTop: 4 }}>
{filtered.length > 0 ? (
filtered.map((item) => (
<li
key={item}
onClick={() => handleSelect(item)}
style={{ cursor: "pointer" }}
>
{item}
</li>
))
) : (
<li>No options</li>
)}
</ul>
)}
</div>
);
}
Top comments (0)