30 Must-Know Frontend Coding Interview Questions: From Promise to Virtual DOM
Covers 30 frontend coding interview questions across 5 categories: JS Basics, Async Programming, Design Patterns, DOM/Browser, and Algorithms, with core approaches and key points for each.
30 Must-Know Frontend Coding Interview Questions: From Promise to Virtual DOM
Introduction
I've interviewed at over a dozen frontend positions, from ByteDance to Pinduoduo, from Kuaishou to Xiaohongshu, and coding questions are unavoidable. Some people ace the theory but panic at coding — because coding questions test not memory, but coding ability and depth of understanding. I've compiled the 30 most frequently asked coding questions from my interviews, organized into five categories: JS Basics, Async Programming, Design Patterns, DOM/Browser, and Algorithms. Each question includes core approach and key points. I recommend typing out every question yourself — just reading won't cut it.
1. JS Basics (10 Questions)
1. Debounce
Core approach: Delay execution; if triggered again during the delay, restart the timer. Key points: Use closure to save timer, clearTimeout then setTimeout on each call. Interviewers often ask: Should the first call execute immediately? Add an immediate parameter — when true, execute on the first call. Also handle this binding and arguments with apply or ...args.
2. Throttle
Core approach: Execute at most once per fixed time interval. Two implementations: Timestamp version (executes immediately on first call, not on last) and Timer version (doesn't execute immediately on first, does on last). Interviewers ask: Can you combine both? Use timestamp + timer for immediate first execution and guaranteed last execution. I was asked to write this combined version at ByteDance's second round.
3. Deep Clone
Basic version: Recursion + type checking. Advanced: Handle circular references (WeakMap cache), special objects (Date, RegExp, Map, Set), Symbol keys, functions (reference only, don't copy). Interviewers love asking about circular references — store copied objects in WeakMap during recursion, return cached result if already copied.
4. call/apply/bind
call: Temporarily mount function on context, execute, then delete. apply: Like call but with array arguments. bind: Returns new function, supports currying. bind key points: 1) Bind this; 2) Save preset arguments; 3) When called with new, this points to instance not bound context; 4) Prototype chain inheritance. At Meituan's first round, I was asked to fully implement bind — the new case must be handled.
5. new Operator
Four steps: 1) Create empty object; 2) Set object's __proto__ to constructor's prototype; 3) Bind constructor's this to new object and execute; 4) If constructor returns an object, return that; otherwise return new object. Note: Only use the return value if it's an object — primitive types are ignored.
6. instanceof
Core approach: Walk up the left object's prototype chain to see if you can find the right constructor's prototype. Use while loop: leftProto = leftProto.__proto__ until null. Follow-up: Can it check primitive types? No, instanceof only works for reference types. Use typeof for primitives.
7. Object.create
Core approach: Create a new object whose __proto__ points to the passed proto parameter. Use temporary constructor: function F(){}, F.prototype = proto, return new F(). Note: proto must be an object or null, otherwise throw TypeError.
8. Currying
Core approach: Transform a multi-argument function into a series of single-argument functions. Use recursion to collect arguments, execute original function when enough arguments collected. Key: Use fn.length for original function's parameter count, use closure to save collected arguments. Interview favorite: add(1)(2)(3) and add(1,2)(3) must both work — collect arguments with ...args concatenation.
9. Array Flatten
Three implementations: 1) Recursive reduce + concat; 2) Stack simulation (iterative, better performance); 3) ES6 flat(Infinity). Interviewers ask: How to implement without recursion? Use a stack: pop each element, if it's an array spread and push back, otherwise add to result. Also support specifying flatten depth.
10. Array Deduplication
Approaches: 1) Set (simplest); 2) filter + indexOf; 3) reduce + includes; 4) Map. Follow-up: How to deduplicate NaN? Set works (NaN === NaN is false but Set considers them equal), indexOf doesn't (NaN's indexOf is -1). Object deduplication? Use Map or JSON.stringify (has ordering issues).
2. Async Programming (6 Questions)
11. Promise
Core: Three states (pending/fulfilled/rejected), state is irreversible. then method returns new Promise for chaining. Key points: 1) resolve/reject use setTimeout for async execution; 2) then's onFulfilled/onRejected are microtasks (use queueMicrotask); 3) Value passthrough — when then has no arguments, value passes down; 4) Exceptions in then go to next reject. At Alibaba's first round, I was asked to write a complete Promise — at minimum implement then and catch.
12. Promise.all
Core approach: Wait until all Promises are fulfilled to fulfill; reject if any one rejects. Use counter to track completions, use array to store results in order. Note: Wrap non-Promise inputs, empty array resolves immediately. Follow-up: How to guarantee result order? Use index instead of push.
13. Promise.race
Core approach: Return the result of the first Promise to change state. Simple implementation — iterate all Promises, return whoever resolves/rejects first. Interviewers ask: Difference from Promise.any? race returns the first to complete (regardless of success/failure), any returns the first to succeed.
14. Promise Concurrency Limit
Core approach: Maintain an execution queue, no more than limit tasks running simultaneously. When one completes, start the next. Implementation: Counter or Promise + recursion. This is extremely high-frequency — I was tested on this at ByteDance, Kuaishou, and Pinduoduo. Key points: 1) Recursively start next task in completion callback; 2) Resolve overall Promise when all tasks complete; 3) Error handling — one failure doesn't affect other tasks.
15. async/await Principles
Essentially syntactic sugar for Generator + auto-executor. Generator yields control, executor calls next() to resume. Hand-write: Recursively call gen.next(), if done is true then resolve, otherwise call then on value (Promise) and recurse. Interviewers ask: Why does async function return Promise? Because the executor wraps it in a Promise.
16. Traffic Light Alternation
Problem: Red 3s → Green 2s → Yellow 1s, loop. Most elegant with async/await: while(true){ await red(); await green(); await yellow(); }. Each light implemented with Promise + setTimeout. Follow-up: Without async/await? Use callback nesting or Promise chaining.
3. Design Patterns (5 Questions)
17. Publish-Subscribe Pattern (EventEmitter)
Core: on to register, emit to trigger, off to remove, once for one-time registration. Use object to map event names to callback arrays. Key points: 1) once wraps with function that auto-offs after execution; 2) emit handles missing events; 3) Pass arguments with ...args. This is the most commonly tested design pattern — I encountered it at 5 companies.
18. Observer Pattern
Difference from publish-subscribe: In observer pattern, Subject directly notifies Observer with no intermediate event bus; publish-subscribe uses EventBus for decoupling. Hand-write: Subject maintains observer list, notify iterates and calls update. Interviewers ask: Which pattern is Vue's reactivity? Observer pattern — Dep (Subject) notifies Watcher (Observer).
19. Singleton Pattern
Core: Ensure a class has only one instance. Implementations: 1) Closure + static method; 2) ES6 class static property; 3) Proxy pattern. Interview favorite: Implement a global Storage singleton. Key: getInstance checks if already created, returns existing if so. I was asked at Pinduoduo: Difference between lazy and eager initialization? Lazy creates on first use, eager creates on class load.
20. Strategy Pattern
Core: Define a family of algorithms, encapsulate each one, make them interchangeable. Classic scenario: Form validation — different rules are different strategies, Validator class calls them. Hand-write: Strategy object + Context class. Each method in strategy object corresponds to a rule, context class receives strategy name and calls corresponding method. Benefit: Avoids massive if-else, new strategies don't affect existing code.
21. Proxy Pattern
Core: Provide a surrogate for another object to control access. Common proxy types: Virtual proxy (image lazy loading), Cache proxy (computation result caching), Protection proxy (access control). Hand-write cache proxy: Create proxy function, internally use Map/object to cache results, same parameters return cached value. Follow-up: Familiar with ES6 Proxy? That's metaprogramming-level proxying, more powerful.
4. DOM/Browser (5 Questions)
22. Event Delegation
Core approach: Use event bubbling, listen on parent element, determine actual triggering child via event.target. Hand-write: Add click listener to ul, check if target.tagName is LI. Follow-up: What if LI has child elements? Use closest method to search upward. At Kuaishou's first round, I was asked to write event delegation and explain the difference between e.target and e.currentTarget.
23. Image Lazy Loading
Option 1: Listen to scroll event + getBoundingClientRect to check if element is in viewport. Option 2: IntersectionObserver (recommended). Hand-write IntersectionObserver version: new IntersectionObserver, when isIntersecting is true, assign data-src to src and unobserve. Interviewers ask: How to optimize scroll version? Add throttle + requestAnimationFrame.
24. Template Engine
Core approach: Replace <%=xxx%> in template string with data. Use regex /<%=(.*?)%>/g to match, replace with values from data. Advanced: Support if/for logic — use Function constructor to dynamically generate render function. Interviewers ask: How to prevent XSS? HTML-escape output, escaping <>&"' characters.
25. Router (Hash Router)
Core: Listen to hashchange event, render corresponding component based on hash. Hand-write: 1) Route class stores path and component; 2) Router class manages routes with add/go/init methods; 3) init listens to hashchange, matches route and renders component. Follow-up: How to implement history routing? Use pushState + popstate event, requires backend support.
26. JSONP
Core: Use script tag's exemption from same-origin policy. Hand-write: 1) Create script tag, src is API URL + callback parameter; 2) Mount callback function on window; 3) Server returns callback(data); 4) Frontend callback receives data; 5) Clean up script and callback. Note: Only supports GET, has XSS risk.
5. Algorithms (4 Questions)
27. LRU Cache
Core data structures: Map (maintains insertion order) or Doubly linked list + HashMap. Map version is simplest: delete then set on get (move to end), delete first key on put if over capacity. Doubly linked list version is more classic: move node to head on get/put, evict tail node. At ByteDance's third round, I was asked to write the doubly linked list version with Node class and LRUCache class.
28. Array Flatten (Recursive/Iterative)
Different from question 9 — interviewers may ask for specified depth. Recursive: reduce + concat, depth-1 each recursion level. Iterative: Use stack, each element tagged with current depth. Follow-up: Sparse arrays? flat method skips sparse slots.
29. Array Deduplication (Multiple Approaches)
Different from question 10 — interviewers may ask for multiple approaches with comparison. Set: [...new Set(arr)]. filter: arr.filter((item, index) => arr.indexOf(item) === index). Map: Use Map to store seen values. Follow-up: NaN handling? Set considers NaN === NaN, indexOf can't find NaN.
30. Sorting Algorithms (Quick Sort/Merge Sort)
Quick sort: Choose pivot, smaller left, larger right, recurse. In-place quick sort is commonly tested: two pointers scanning from both ends, swap positions. Merge sort: Divide and conquer, recursively split in half then merge. Both O(nlogn), but quick sort worst case O(n²), merge sort is stable. Interviewers ask: When to use merge sort? Large data with stable sorting requirement. At Meituan's second round, I was asked to write in-place quick sort.
Summary of Real Questions
The above 30 questions ranked by frequency: Top 10 are debounce/throttle, deep clone, call/apply/bind, Promise, Promise.all, concurrency limit, EventEmitter, LRU cache, new operator, instanceof. Promise-related and EventEmitter are tested at almost every interview — you must know them inside out.
Tips and Advice
1. You must type every question yourself — understanding and writing are worlds apart. Practice on LeetCode or CodePen.
2. Watch for edge cases — interviewers love to trap you here: null/undefined arguments, empty arrays, circular references, NaN handling, etc.
3. Explain your approach before coding — confirm understanding with the interviewer first to avoid writing the wrong thing.
4. Test after writing — construct test cases and run them. Interviewers value verification awareness.
5. Understand the principles — not just being able to write it, but explaining why. Like why Promise uses microtasks, why LRU uses doubly linked lists.
FAQ
Q: How complete should my code be?
A: Core logic must be complete, edge cases should be covered as much as possible. Don't need 100% perfection, but main functionality should work. Interviewers value approach and coding habits more.
Q: How to prioritize with limited time?
A: Focus on Top 10 high-frequency questions first, especially Promise series, debounce/throttle, deep clone, EventEmitter. These cover 80% of tested topics.
Q: What language for coding questions?
A: Frontend positions use JavaScript/TypeScript. If the interviewer allows TS, type annotations are a plus.
Q: What if I can't write it?
A: Explain your approach first — even if code is incomplete, correct thinking earns points. Never stay silent — think out loud, let the interviewer see your thought process.