Coding Tasks
Practical algorithm and closure tasks — arrays, strings, recursion, method chaining, and output prediction.
19 questions
JuniorCodeVery commonBuild a counter factory using a closure
Build a counter factory using a closure
Declare a local let count = 0 inside createCounter, then return an object whose inc and get methods close over that variable. The returned methods keep a live reference to count after the factory returns, so it stays private. Each call to the factory creates a fresh count, giving independent counters.
Common mistakes
- ✗Putting
counton the returned object as a public field instead of a closed-over local - ✗Assuming all counters share one count because they share the factory function
- ✗Believing
vargives block scope — it is function-scoped, not block-scoped
Follow-up questions
- →Why does each call to
createCounterproduce an independentcount? - →How would you add a
reset()method that closes over the same variable?
JuniorCodeVery commonWhat does this event-loop ordering snippet print, and why?
What does this event-loop ordering snippet print, and why?
It prints A, D, C, B. The two synchronous console.log calls run first, so A then D. The Promise.then callback is a microtask and the setTimeout callback is a macrotask; after the synchronous code finishes, all microtasks drain before any macrotask, so C prints before B even though the timeout delay is 0.
Common mistakes
- ✗Thinking callbacks fire in source order rather than by queue type
- ✗Believing
setTimeout(…, 0)runs before pending microtasks - ✗Assuming the engine blocks on
setTimeoutor the promise inline
Follow-up questions
- →Where do
queueMicrotaskandawaitcontinuations sit in this ordering? - →Would adding a second
.thenchange whereBprints?
SeniorCodeVery commonImplement debounce(fn, delay)
Implement debounce(fn, delay)
Keep a timeoutId in the closure. The returned function calls clearTimeout(timeoutId) to cancel any pending run, then timeoutId = setTimeout(...) to schedule fn after delay. Inside the timer call fn.apply(this, args) to preserve this and forward arguments. Only the last call's timer survives, so fn runs once after activity stops.
Common mistakes
- ✗Confusing debounce with throttle — debounce waits for a quiet period, not a fixed rate
- ✗Forgetting
clearTimeout, so every call fires after its own delay instead of just the last - ✗Losing
this/arguments by callingfn()directly instead offn.apply(this, args)
Follow-up questions
- →How would you add a leading-edge option that fires on the first call too?
- →How does the closed-over
timeoutIdsurvive between separate calls?
JuniorCodeCommonFlatten an arbitrarily nested array
Flatten an arbitrarily nested array
Iterate the array with reduce; for each element, if Array.isArray is true, recurse into it and concat the flattened result, otherwise push the value. The recursion is what reaches every depth. An empty array reduces to [], and the order of elements is preserved across all levels.
Common mistakes
- ✗Flattening only one level (a bare
concat) and missing deeper nesting - ✗Checking
typeof x === 'object'instead ofArray.isArray, mishandling nulls and objects - ✗Mutating the input array while recursing instead of building a new one
Follow-up questions
- →How would you flatten to only a fixed depth
ninstead of all the way down? - →Why is
Array.isArraysafer thaninstanceof Arrayacross realms?
JuniorCodeCommonWhat does this type-coercion snippet print, and why?
What does this type-coercion snippet print, and why?
Lines print '33', 6, 4, and '[object Object]'. + evaluates left to right: (1 + 2) is 3, then 3 + '3' concatenates to '33' because one operand is a string. * and - force numbers, so '3' * 2 is 6 and '5' - 1 is 4. In [] + {}, [] stringifies to '' and {} to '[object Object]', joining to '[object Object]'.
Common mistakes
- ✗Thinking
+always sums — with any string operand it concatenates - ✗Assuming
-and*concatenate like+does on strings - ✗Expecting
{}to stringify to'{}'rather than'[object Object]'
Follow-up questions
- →Why does
+evaluate strictly left to right in1 + 2 + '3'? - →Why does
[] + {}give'[object Object]'rather than an error?
JuniorDebuggingCommonWhat does this mutate-vs-reassign snippet print, and why?
What does this mutate-vs-reassign snippet print, and why?
{ a: 5, b: 2 }, then { a: 5, b: 2 }, then 5. JavaScript passes the object's reference by value: fn1 mutates the shared object through the parameter, so the change is visible outside. fn3 only reassigns its local parameter to 5 — that rebinds the local name, leaving the outer object untouched. fn4 reassigns the outer x itself (no parameter shadows it), so x becomes the primitive 5.
Common mistakes
- ✗Thinking reassigning a parameter (
x = 5) changes the caller's variable like property mutation does - ✗Calling JavaScript pure pass-by-reference for objects — it passes the reference by value
- ✗Assuming a parameter named like an outer variable shares the same binding
Follow-up questions
- →Why does mutating a property show outside the function but reassigning the parameter does not?
- →How does
fn4reach the outerxwhilefn3cannot?
JuniorCodeCommonReturn the unique values of an array
Return the unique values of an array
Pass the array into a Set, which drops duplicates while keeping insertion order, then spread it back: [...new Set(arr)]. Set membership uses SameValueZero, so it dedups primitives correctly (and treats NaN as equal to itself). This is O(n) and never mutates the input.
Common mistakes
- ✗Sorting to dedup, which destroys the original order of first appearance
- ✗Assuming a
Setdeduplicates objects by value — it dedups by reference - ✗Using
indexOfinside a loop, turning an O(n) job into O(n²)
Follow-up questions
- →How does
Setmembership handleNaNdifferently from===? - →How would you dedup an array of objects by a specific property?
SeniorCodeCommonImplement curry(fn)
Implement curry(fn)
Return a recursive curried(...args): if args.length >= fn.length, call fn(...args); otherwise return a new function that takes more arguments and calls curried(...args, ...next), accumulating them in the closure. fn.length (the declared arity) is the threshold, so all the partial-application forms collect arguments until enough are gathered.
Common mistakes
- ✗Hard-coding the arity instead of reading
fn.length - ✗Assuming exactly one argument per call, breaking the
(1, 2)(3)form - ✗Sharing one mutable args array across branches so concurrent partials interfere
Follow-up questions
- →Why is
fn.lengththe right threshold, and when does it report the wrong arity? - →How would you support placeholder arguments to skip a parameter position?
SeniorCodeCommonDeep-merge two plain objects
Deep-merge two plain objects
Start with a shallow copy { ...a }. For each key of b, if both a[key] and b[key] are plain objects, set the result key to deepMerge(a[key], b[key]) recursively; otherwise assign b[key]. The recursion merges nested objects, and copying first means neither input is mutated. Non-object values from b overwrite.
Common mistakes
- ✗Using
Object.assignor spread, which only merges the top level (shallow) - ✗Mutating
ain place instead of building and returning a new object - ✗Treating arrays or
nullas plain objects to recurse into
Follow-up questions
- →How would you decide whether a value is a 'plain object' versus an array or
null? - →How should the merge behave when one key holds an array on each side?
SeniorCodeCommonWhat does this this-binding snippet print, and why?
What does this this-binding snippet print, and why?
It prints obj, then '' three times. this is set by the call site, not where the function is defined. obj.method() binds this to obj. The detached fn() and the setTimeout callback are plain calls, so this is the global object (window.name is ''). The arrow has no own this; it inherits the module's outer this, also giving ''.
Common mistakes
- ✗Thinking
thisis fixed at definition rather than set by the call site - ✗Assuming an arrow inside an object literal binds
thisto that object - ✗Expecting a detached method to keep its original receiver
Follow-up questions
- →How would the detached
fn()behave differently in strict mode? - →How does passing
obj.method.bind(obj)tosetTimeoutchange the output?
SeniorCodeCommonImplement throttle(fn, limit)
Implement throttle(fn, limit)
Track lastRun (timestamp) in the closure, starting so the first call passes. On each call compute now = Date.now(); if now - lastRun >= limit, set lastRun = now and run fn.apply(this, args), otherwise ignore the call. This caps fn to one run per limit window on the leading edge while preserving this and arguments.
Common mistakes
- ✗Confusing throttle with debounce — throttle caps the rate, debounce waits for quiet
- ✗Initializing
lastRunso the very first call is wrongly skipped - ✗Losing
this/arguments by callingfn()directly instead offn.apply(this, args)
Follow-up questions
- →How would you add a trailing call so the final invocation is not lost?
- →Why does the leading-edge version fire the first call immediately?
JuniorCodeOccasionalMake an object's methods chainable so calls can be written in a fluent sequence
Make an object's methods chainable so calls can be written in a fluent sequence
Each mutating method updates the instance state and then return this. Returning the same object lets the next call in the chain operate on it, so append('a').append('b').upper() reads as one fluent sequence. The terminal value getter returns the actual result, since it ends the chain — exactly how jQuery achieves fluent APIs.
Common mistakes
- ✗Returning the mutated field (
this.str) instead ofthis, which breaks the next method call - ✗Returning nothing (
undefined), so the second method in the chain throws - ✗Mutating in
appendbut forgettingupper()must alsoreturn thisto stay chainable
Follow-up questions
- →Why must a chainable method return
thisrather than the updated value? - →How would a non-chainable terminal method like
valuediffer from the chainable ones?
JuniorCodeOccasionalCompute a factorial recursively
Compute a factorial recursively
Base case: when n is 0 (or 1), return 1. Recursive case: return n * factorial(n - 1). Each call multiplies n by the factorial of n - 1 until the base case stops the recursion. The base case at 0 is what makes factorial(0) correctly yield 1.
Common mistakes
- ✗Omitting the base case, causing infinite recursion and a stack overflow
- ✗Recursing toward
n + 1instead ofn - 1, so it never terminates - ✗Returning
0for the base case, which zeroes out the whole product
Follow-up questions
- →Why does a missing base case cause a
RangeErrorstack overflow? - →How would you rewrite this iteratively to avoid deep recursion?
JuniorCodeOccasionalLoad an image on click and show placeholder, success, and error states
Load an image on click and show placeholder, success, and error states
Clear the container (replaceChildren()), create an img, set its placeholder background grey, and attach handlers BEFORE setting src: img.onload reveals the image, img.onerror sets a red background. Setting src last kicks off the load, firing exactly one of the two events. Clearing first means a second click replaces the previous result.
Common mistakes
- ✗Setting
srcbefore attachingonload/onerror, risking a missed event on a cached image - ✗Expecting image loading to be synchronous (reading
completeright aftersrc) instead of event-driven - ✗Forgetting to clear the container first, so clicks stack images instead of replacing them
Follow-up questions
- →Why attach
onload/onerrorbefore assigningsrc? - →Which event fires when the image URL is broken, and which on success?
JuniorCodeOccasionalReverse a string in O(n)
Reverse a string in O(n)
Split into characters, reverse, and join: s.split('').join after .reverse(), or [...s].reverse().join(''). The spread form handles multi-byte code points better than split(''). A manual loop building the result from the last index down also works in O(n) time and returns '' for the empty string.
Common mistakes
- ✗Calling
reverseon the string directly — only arrays havereverse - ✗Using
split('')and assuming it handles surrogate pairs and emoji correctly - ✗Forgetting that strings are immutable, so a new string must be returned
Follow-up questions
- →Why does
[...s]split surrogate pairs more correctly thans.split('')? - →How would you reverse the words in a sentence but not the letters?
MiddleCodeOccasionalBuild a chainable calculator with a private, read-only value
Build a chainable calculator with a private, read-only value
Keep the running total in a closure variable. Return an object whose add/div mutate it and return this for chaining. Expose the total through a get value() accessor only — with no setter, calc.value = 100 is silently ignored, so the state stays private. In div, check the divisor and throw before dividing, so a divide-by-zero leaves the total untouched.
Common mistakes
- ✗Exposing the total as a writable property instead of a getter, so outside code can corrupt it
- ✗Dividing before the zero check, so
div(0)mutates the value (toInfinity/NaN) before throwing - ✗Returning the number from
add/divinstead ofthis, breaking the chain
Follow-up questions
- →How does a getter-only
valuekeep the total private compared with a public field? - →How would
#privateclass fields achieve the same encapsulation as the closure variable?
MiddleCodeOccasionalBuild a chainable jQuery-style $ wrapper over multiple DOM nodes
Build a chainable jQuery-style $ wrapper over multiple DOM nodes
Store the querySelectorAll result on the instance. Each method loops over this.elements applying the change — classList.add/remove, Object.entries(obj) into el.style, el.innerHTML = s — then return this so the wrapper chains. The methods live once on the class prototype rather than being recreated per element, and one call fans the operation across the whole matched set.
Common mistakes
- ✗Using
querySelector(one node) when the API must operate on every matched element - ✗Forgetting
return this, so the second method in the chain has no wrapper to call on - ✗Recreating the wrapper or methods per element instead of defining them once on the class
Follow-up questions
- →Why is a class wrapper more efficient than returning a fresh object of methods on each
$()call? - →How does
return thisover a node list let one call fan an operation across many elements?
MiddleCodeOccasionalFind the one number that appears once while all others appear twice
Find the one number that appears once while all others appear twice
XOR every element together — nums.reduce((a, b) => a ^ b, 0). XOR is commutative and associative, x ^ x === 0, and x ^ 0 === x, so each pair cancels to zero and only the lone value survives. This is O(n) time and O(1) space, beating the hash-count baseline that needs O(n) memory.
Common mistakes
- ✗Reaching for a hash map or
Setcount, which solves it but at O(n) space - ✗Forgetting that XOR's identity is
0, so the reduce must seed the accumulator with0 - ✗Assuming XOR only works on sorted input — order does not matter to it
Follow-up questions
- →How would the XOR approach change if every other element appeared three times?
- →Why does seeding
reducewith0rather thannums[0]keep the code correct?
SeniorCodeOccasionalCheck a string is a palindrome in O(n) time, O(1) space
Check a string is a palindrome in O(n) time, O(1) space
Two pointers l = 0 and r = s.length - 1 move inward. Skip any side whose character fails a letter test (e.g. a regex like /[a-z]/i), then compare with toLowerCase; a mismatch returns false. Meeting in the middle returns true. This is O(n) time and O(1) extra space — no cleaned copy is built.
Common mistakes
- ✗Building a cleaned or reversed copy, which costs O(n) extra space instead of O(1)
- ✗Comparing characters without lowercasing both sides first
- ✗Forgetting to skip non-letters on BOTH sides before each comparison
Follow-up questions
- →How would you also treat digits as significant characters?
- →Why does skipping non-letters inside the loop keep it O(n) overall?