Microsoft Frontend Interview Questions
Coding rounds, with TypeScript and React close behind.
Microsoft's web surface is large: Teams, Outlook on the web, Office, the Azure Portal and VS Code are all built with web technology, and TypeScript itself came out of Microsoft. Many product teams use React with TypeScript, so typed code and component design come up more here than at most companies.
The loop still leans on problem solving. Expect data structure questions next to JavaScript and UI questions, and a design discussion sized to your level.
- Questions
- 15, 5 with free answers
- Interview rounds
- 5
- Last reviewed
- October 2026
Microsoft frontend interview process
The usual shape of the loop. A guide to what to prepare, not a promise of what you will get.
- 1
Online assessment or phone screen
Coding problems. More common in campus and early-career hiring.
- 2
Coding rounds
Two or more interviews on data structures and algorithms, with clean code and test cases expected.
- 3
Frontend round
JavaScript and TypeScript fundamentals, React, accessibility, and often a small component to build.
- 4
Design round
Low-level design for early levels, frontend or system design for senior ones.
- 5
Final round
Often with a senior manager, sometimes called the as-appropriate round: a mix of technical depth and behavioural questions.
What interviewers tend to look for
- TypeScript beyond the basics: generics, utility types and typing React props.
- Accessibility, which Microsoft treats as a product requirement.
- Testable, readable code and how you would test it.
- Steady problem solving on medium-level algorithm questions.
Your Microsoft mock interview
15 questions. Type an answer under each one and an AI reviewer scores it and tells you what you missed. Stuck on one? Ask for a hint. A free account gets 3 AI replies a day.
Your mock interview
0 of 15 answered
Answer a question and your score shows up here.
JavaScript
4 questions- Question 1 of 15JavaScript
Microsoft interviewer
Explain hoisting and the temporal dead zone.
Model answer
Declarations are registered when a scope is created, before any of its code runs. What differs is the starting value:
varis hoisted and set toundefined, so reading it early givesundefined.- Function declarations are hoisted with their body, so you can call them above where they are written.
let,constandclassare hoisted but left uninitialised. Reading them before the declaration line throws aReferenceError.
The stretch between the start of the scope and the declaration line is the temporal dead zone.
console.log(a); // undefined console.log(b); // ReferenceError var a = 1; let b = 2;Function expressions follow their variable:
var fn = function () {}isundefineduntil that line runs. - Question 2 of 15JavaScript
Microsoft interviewer
Implement Promise.all.
Model answer
function promiseAll(iterable) { return new Promise((resolve, reject) => { const items = Array.from(iterable); const results = new Array(items.length); let pending = items.length; if (pending === 0) return resolve(results); items.forEach((item, index) => { Promise.resolve(item).then((value) => { results[index] = value; // by index, not by finish order pending -= 1; if (pending === 0) resolve(results); }, reject); }); }); }What the interviewer checks:
- Results keep the input order, whichever promise finishes first.
- Plain values are accepted, which is what
Promise.resolve(item)handles. - The first rejection rejects the whole thing.
- An empty input resolves straight away with an empty array.
Expect a follow-up asking for
Promise.allSettled,Promise.anyorPromise.race. - Question 3 of 15JavaScript
Microsoft interviewer
Compare Promise.all, allSettled, race and any. How do you run async calls in parallel with async/await?
Model answer
Promise.all: fulfils with every value, rejects on the first rejection.Promise.allSettled: never rejects. It reports the outcome of each promise.Promise.race: settles with whichever promise settles first, fulfilled or rejected.Promise.any: fulfils with the first success, and rejects with anAggregateErroronly if all fail.
A common mistake is awaiting inside a loop, which runs the calls one after another. Start them first, then await them together:
// Sequential: the second call waits for the first const user = await getUser(id); const orders = await getOrders(id); // Parallel: both start immediately const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);Wrap awaited calls in
try/catch. UseallSettledwhen one failure should not throw away the other results. - Question 4 of 15JavaScript
Microsoft interviewer
Implement an EventEmitter with on, off, once and emit.
Model answer
class EventEmitter { #listeners = new Map(); on(event, listener) { if (!this.#listeners.has(event)) this.#listeners.set(event, new Set()); this.#listeners.get(event).add(listener); return () => this.off(event, listener); } off(event, listener) { this.#listeners.get(event)?.delete(listener); } once(event, listener) { const unsubscribe = this.on(event, (...args) => { unsubscribe(); listener(...args); }); return unsubscribe; } emit(event, ...args) { // Copy first: a listener may remove itself while we iterate. for (const listener of [...(this.#listeners.get(event) ?? [])]) { listener(...args); } } }Points to raise:
onreturns an unsubscribe function,emititerates over a copy, and aSetprevents the same listener being added twice. Ask whether one listener throwing should stop the others; wrapping each call intry/catchis the usual answer.
TypeScript
5 questions- Question 5 of 15TypeScript
Microsoft interviewer
What is the difference between a type alias and an interface?
Model answer
Both can describe the shape of an object, and for that job they are close to interchangeable. The differences:
- An
interfacecan be declared more than once and the declarations merge. That is how you add a field toWindowor to a library's types. - An
interfaceextends others withextends. Atypecombines with&. - A
typecan name anything: unions, tuples, primitives, mapped types and conditional types. An interface can only describe an object or function shape.
type Status = 'idle' | 'loading' | 'error'; // only a type can do this interface User { id: string; name: string; } interface Admin extends User { permissions: string[]; }A sensible rule to state: use either for object shapes, use
typewhen you need a union or a computed type, and stay consistent within a codebase. - An
- Question 6 of 15TypeScript
Microsoft interviewer
Explain any, unknown and never.
Model answer
anyswitches type checking off for a value, and it spreads: anything derived from it is alsoany.unknownalso accepts any value, but you cannot use it until you narrow it. It is the right type for parsed JSON, a caught error and anything from outside your program.neveris the type with no values. A function that always throws returnsnever, and a union that has been fully narrowed isnever. That gives you an exhaustiveness check:type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number }; function area(shape: Shape): number { switch (shape.kind) { case 'circle': return Math.PI * shape.r ** 2; case 'square': return shape.side ** 2; default: { // Add a new Shape and forget a case: this line stops compiling. const unreachable: never = shape; throw new Error('Unhandled shape: ' + JSON.stringify(unreachable)); } } } - Question 7 of 15TypeScript
Microsoft interviewer
What are generics, and how do you constrain one?
Model answer
A generic is a type parameter. It lets a function or type work with many types while keeping the link between its inputs and its output, which
anythrows away.function first<T>(items: T[]): T | undefined { return items[0]; } first([1, 2, 3]); // number | undefined first(['a', 'b']); // string | undefinedA constraint with
extendslimits what the parameter can be, and lets you use the properties that the constraint guarantees:function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] { return items.map((item) => item[key]); } pluck([{ id: 1, name: 'Asha' }], 'name'); // string[] pluck([{ id: 1, name: 'Asha' }], 'age'); // compile errorGood to mention: type arguments are usually inferred, generics can have defaults (
<T = string>), and one that is used only once in a signature is usually not needed. - Question 8 of 15TypeScript
Microsoft interviewer
Which utility types do you use, and can you implement Pick and Omit yourself?
Model answer
The everyday ones:
Partial,Required,Readonly,Pick,Omit,Record,ReturnType,Parameters,AwaitedandNonNullable.They are built from mapped types, which loop over keys with
in:type MyPartial<T> = { [K in keyof T]?: T[K] }; type MyPick<T, K extends keyof T> = { [P in K]: T[P] }; type MyOmit<T, K extends PropertyKey> = MyPick<T, Exclude<keyof T, K>>; type MyRecord<K extends PropertyKey, V> = { [P in K]: V };A typical use is deriving one type from another so they cannot drift apart:
interface User { id: string; name: string; email: string; } type NewUser = Omit<User, 'id'>; type UserPatch = Partial<NewUser>;A common harder follow-up is
DeepPartialorDeepReadonly, which apply the same mapping recursively. - Question 9 of 15TypeScript
Microsoft interviewer
How do you type React components, props, events and hooks?
Model answer
type ButtonProps = React.ComponentProps<'button'> & { variant?: 'primary' | 'ghost'; }; function Button({ variant = 'primary', children, ...rest }: ButtonProps) { return ( <button data-variant={variant} {...rest}> {children} </button> ); } function Search() { const [query, setQuery] = useState(''); // inferred as string const [user, setUser] = useState<User | null>(null); const inputRef = useRef<HTMLInputElement>(null); const onChange = (event: React.ChangeEvent<HTMLInputElement>) => { setQuery(event.target.value); }; return <input ref={inputRef} value={query} onChange={onChange} />; }Points worth saying out loud:
React.ComponentProps<'button'>gives you every native button prop, so you do not retypeonClickanddisabled.childrenisReact.ReactNode.- Let
useStateinfer simple types, and pass a type argument when the initial value isnullor an empty array. - Components can be generic:
function List<T>({ items, renderItem }: ListProps<T>)keeps the item type through torenderItem.
React
3 questions- Question 10 of 15React
Microsoft interviewer
How does useEffect work? Explain the dependency array, cleanup and the common mistakes.
Model answer
An effect runs after React has committed a render. Its job is to keep something outside React in step with your state: a subscription, a timer, a DOM API or a network request.
- No dependency array: runs after every render.
- An empty array: runs once after the first render.
- With dependencies: runs again when any of them changes, compared with
Object.is. - The function you return is the cleanup. It runs before the next run of the effect and when the component unmounts.
useEffect(() => { const controller = new AbortController(); fetch('/api/users/' + id, { signal: controller.signal }) .then((response) => response.json()) .then(setUser) .catch((error) => { if (error.name !== 'AbortError') setError(error); }); return () => controller.abort(); }, [id]);Mistakes interviewers look for: leaving a dependency out and reading a stale value, depending on an object or function that is recreated every render (an endless loop), not handling a response that arrives after
idhas changed, and using an effect to compute something from props that could be computed during render.In development, Strict Mode mounts each component, unmounts it and mounts it again, to expose effects that do not clean up.
- Question 11 of 15React
Microsoft interviewer
What is an error boundary, and what does it not catch?
Model answer
An error boundary is a component that catches an error thrown while rendering anything below it and shows a fallback instead of unmounting the whole app.
It is still written as a class, with
static getDerivedStateFromErrorto switch to the fallback andcomponentDidCatchto log. Most teams use thereact-error-boundarypackage instead of writing their own.<ErrorBoundary fallback={<p>Could not load orders.</p>} onError={report}> <Orders /> </ErrorBoundary>It does not catch:
- Errors in event handlers. Use
try/catchthere. - Errors in asynchronous code such as timers and promise callbacks.
- Errors thrown in the boundary itself.
Place one around each route, and a smaller one around any widget that can fail on its own, so one broken chart does not blank the page. Give the user a way to retry, usually by resetting the boundary.
- Errors in event handlers. Use
- Question 12 of 15React
Microsoft interviewer
How do you test React components?
Model answer
The usual stack is React Testing Library with Vitest or Jest, and Playwright or Cypress for end-to-end tests.
The principle: test what a user can see and do, not how the component is built. Find elements by role and label, interact with them, and assert on what appears.
test('shows an error for an invalid email', async () => { const user = userEvent.setup(); render(<SignupForm />); await user.type(screen.getByLabelText('Email'), 'not-an-email'); await user.click(screen.getByRole('button', { name: 'Sign up' })); expect(await screen.findByRole('alert')).toHaveTextContent('valid email'); });- Querying by role also checks that the markup is accessible.
- Use
findByqueries for anything that appears after an async update. - Mock the network at the request level with a tool such as MSW, not by mocking your own modules.
- Do not assert on state, on hooks being called or on class names.
A reasonable split: unit tests for pure functions and hooks, component tests for forms and other interactive pieces, and a few end-to-end tests for the paths that earn money.
CSS
1 question- Question 13 of 15CSS
Microsoft interviewer
When do you use Flexbox and when do you use Grid?
Model answer
Flexbox lays items out along one axis, a row or a column, and lets their content decide their size. Use it for toolbars, navigation, a row of buttons, or centring one thing.
Grid lays items out in rows and columns at the same time, and the container decides the tracks. Use it for page layouts, card grids and anything where items must line up in both directions.
/* Cards that wrap by themselves, with no media queries */ .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; } /* A header with the actions pushed to the right */ .header { display: flex; align-items: center; gap: 12px; } .header .actions { margin-left: auto; }They are used together: Grid for the page, Flexbox inside the pieces.
Two gotchas that come up:
flex: 1means1 1 0%, and a flex or grid item will not shrink below its content width unless you setmin-width: 0, which is the usual cause of long text overflowing.
Browser, performance and system design
1 question- Question 14 of 15Browser, performance and system design
Microsoft interviewer
How do you make a web page accessible?
Model answer
- Use the right HTML element. A
buttonis focusable, works with Enter and Space and is announced as a button. Adivwith a click handler does none of that. The same goes fornav,main, headings in order, and alabelfor every input. - Keyboard. Everything must be reachable and usable without a mouse, in a sensible order, with a visible focus ring. When a dialog opens, move focus into it, keep it there, and return it to the trigger on close.
- Text alternatives. Images need
alttext, or an emptyaltif they are decorative. Icon-only buttons need an accessible name. - Colour. Body text needs a contrast ratio of at least 4.5 to 1, and colour should never be the only signal.
- ARIA, when HTML is not enough.
aria-expandedon a toggle,aria-livefor updates that appear without a page load, roles for custom widgets. No ARIA is better than wrong ARIA. - Motion and zoom. Respect
prefers-reduced-motion, and make sure the page works at 200% zoom.
For testing: use the page with the keyboard only, run an automated checker such as axe or Lighthouse, and try a screen reader on the main flows. Automated tools catch only part of the problems.
- Use the right HTML element. A
Machine coding
1 question- Question 15 of 15Machine coding
Microsoft interviewer
Build a data table with sorting, search and pagination.
Model answer
Keep the raw rows and the controls in state:
sort(keyanddirection),queryandpage. Everything shown is derived from those on each render.const visibleRows = useMemo(() => { const filtered = rows.filter((row) => row.name.toLowerCase().includes(query.toLowerCase()) ); const sorted = sort.key ? [...filtered].sort((a, b) => { const result = a[sort.key] > b[sort.key] ? 1 : a[sort.key] < b[sort.key] ? -1 : 0; return sort.direction === 'asc' ? result : -result; }) : filtered; const start = (page - 1) * pageSize; return { total: sorted.length, rows: sorted.slice(start, start + pageSize) }; }, [rows, query, sort, page, pageSize]);- Copy before sorting.
sortchanges the array in place. - Go back to page 1 when the search or sort changes.
- Clicking a header cycles ascending, descending, none, and shows an arrow.
- Drive columns from a config array (
key,label,sortable, optionalrender). - Use real
tablemarkup, witharia-sorton the sorted header. - Show an empty state when nothing matches.
Follow-ups: row selection with a select-all checkbox that has an indeterminate state, doing the sorting and paging on the server, a sticky header, and virtualising for very large data sets.
- Copy before sorting.
Practise for Microsoft
Reading answers is the easy half. These problems run in the browser against real tests.
