What I Ask Juniors Before Extending an Offer
The four questions I lean on when I am the last interviewer for a junior loop. They are not trick questions: each one tells me whether the candidate has actually built something or only studied for the interview.
By @hanaokoro
November 25, 2025
·
Updated May 20, 2026
277 views
6
Rate
Given an array of { id, name } objects, return a Map keyed by id with the object as the value. I am watching for: do they reach for the right tool, do they handle duplicate ids honestly, and can they tell me the runtime?
Quick check
Quick check: indexById([{ id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' }]) returns Map { 'a' => { id: 'a', name: 'Alice' }, 'b' => { id: 'b', name: 'Bob' } }. On duplicates, indexById([{ id: 'a', name: 'A1' }, { id: 'a', name: 'A2' }]) returns Map { 'a' => { id: 'a', name: 'A2' } } under last-write-wins. The candidate I want flags that convention out loud.
function indexById(items) {
// TODO
}
// indexById([{ id: 'a', name: 'Alice' }, { id: 'b', name: 'Bob' }])
// -> Map { 'a' => { id: 'a', name: 'Alice' }, 'b' => { id: 'b', name: 'Bob' } }Implement debounce(fn, ms) such that calling the returned function repeatedly only fires fn once, ms after the last call. I am watching for: do they preserve this and arguments, do they handle a cancel, and do they know the difference between this and throttle.
Quick check
const d = debounce(console.log, 200);
d(1); d(2); d(3); // all within 200ms -> logs 3, once, 200ms after the last call
d.cancel(); // before the timer fires -> nothing is loggedfunction debounce(fn, ms) {
// TODO
}Read this fetch wrapper. The candidate spots await response.json() is called on a response that may be a 500 with an HTML body. Walk me through how you would change the function to make that case actually fail loudly.
Quick check
Quick check: api('/users') on a 500 response with HTML body throws ApiError with .status === 500 and .body containing a snippet of the HTML, instead of crashing on JSON.parse. On a 204 No Content the helper returns null because the empty body is detected before parsing.
async function api(url, options = {}) {
const res = await fetch(url, options);
return await res.json();
}
// Real failure example: res = 500, body = '<html>...nginx default error page...</html>'
// res.json() throws SyntaxError, callers see a confusing parse error.A user list page fetches users on mount and shows a spinner. The candidate is asked to add a search box that filters as the user types. I am watching for: do they re-fetch on every keystroke, do they debounce, do they understand AbortController?
Quick check
// User types 'al' then 'alice' within 50ms
// -> single fetch for 'alice' fires 200ms after the last keystroke (debounce collapses the rapid keys)
// If a slow 'al' response was already in flight when 'alice' fires:
// -> the 'al' request is aborted via AbortController, so a stale response cannot overwrite the fresh one// Pseudo-React mounted snippet they walk through:
// const [users, setUsers] = useState([]);
// useEffect(() => { fetch('/users').then(r => r.json()).then(setUsers); }, []);
// On input change, what do they do?