Interview Prep
Problems modelled on what technical interviews ask. Solve them in JavaScript, TypeScript, Python or SQL.
Difficulty
Topics
Language
Real debounce is timing-based and hard to test deterministically. Instead, implement solve(events, wait) that simulates trailing-edge debounce over a fixed timeline. events is a list of [timestampMs, value] pairs representing calls to a debounced function. Return a list of [emitTimeMs, value] pairs that the debounced function would actually fire. Semantics (trailing edge) Every call resets the timer. The timer fires wait ms after the last call inside a quiet window. If a new call arrives after the previous schedule fired, the previous emission stays in the result. Example solve([[0,'a'], [50,'b'], [200,'c']], 100) → [[150,'b'], [300,'c']]
Implement a minimal Observable: solve(ops) replays a sequence of operations and returns all emitted values. Operations: { type: 'next'|'error'|'complete'|'unsubscribe', value? }. Emissions after complete or unsubscribe must be silently ignored.
Match a path against a glob pattern. Support: ` matches any sequence except /. ? matches any single char except /. matches any sequence including /. solve('src/.ts', 'src/index.ts') → true`.
The function below mutates its input array — a classic source of bugs. Refactor solve(cart, item) so it returns a new sorted array without modifying cart. After calling solve(cart, item), the original cart array must be unchanged.
Either represents a value that is one of two things: a Right (success) or a Left (failure/error). Unlike Maybe, Left carries information about what went wrong. Implement Right and Left classes, each with: static of(value) .map(fn) — Right applies fn, Left ignores it .fold(leftFn, rightFn) — calls the appropriate function
Defensive null checks scattered throughout code are a smell. Refactor using a Null Object: create a GUEST_USER constant that provides safe defaults, then use it when user is falsy. solve(null) → { name: 'Anonymous', email: 'no-email@placeholder.com', role: 'guest' }
Method chaining creates readable, declarative code. Each method returns this to allow chaining. Implement createPipeline(items) that returns a fluent builder with filter(fn), map(fn), take(n) and value() methods — all chainable. createPipeline([1,-2,3,4,5]).filter(x=>x>0).map(x=>x*2).take(2).value() → [2, 6]
Deep inheritance chains are brittle. When requirements change, you end up fighting the hierarchy. Refactor using composition: create createAnimal(name, sound) that returns an object with a speak() method. No classes, no inheritance. solve(name, sound) calls createAnimal(name, sound).speak(). solve('Rex', 'woof') → 'Rex says: woof'
Partial application pre-fills some arguments of a function, returning a new function that accepts the rest. Implement partial(fn, ...presetArgs) that returns a new function pre-filled with presetArgs. solve(label, ...args) dispatches to predefined partials: 'add5' → partial of (a,b)=>a+b with first arg 5 'multiply3' → partial of (a,b)=>a*b with first arg 3 'greet' → partial of (greeting,name)=>greeting+' '+name with 'Hello'
compose applies functions right-to-left: the last function runs first. Implement compose(...fns) that returns a function applying the array of functions right-to-left. solve(x, label) dispatches to a predefined composition: 'double_addOne' → compose(double, addOne) 'square_double' → compose(square, double) 'addOne_square_double' → compose(addOne, square, double)
Define a family of interchangeable algorithms, encapsulate each one, and select between them at runtime — a sorter that can switch between ascending and descending order without changing Sorter itself. Implement AscendingStrategy/DescendingStrategy (sort(arr)) and Sorter.sort(arr), which delegates to whichever strategy it holds. solve(arr, strategyName) picks the strategy by name and returns the sorted array (the original arr must be left untouched).
Ensure a class has only one instance, and provide a global point of access to it — every call to getInstance() must return the exact same object, so state persists across "modules" that each grab their own reference. Implement Counter.getInstance() so it lazily creates the instance once and reuses it forever after, plus increment()/getValue() on that instance. solve(ops) runs a sequence of 'increment' | 'getValue' operations against Counter.getInstance(), collecting the results of every 'getValue' call. solve(['increment', 'increment', 'getValue']) → [2] — both increments landed on the same instance.
Create new objects by cloning an existing instance rather than instantiating from scratch — the clone must be fully independent: mutating it must never affect the original. Implement ShapePrototype.clone(), returning a new, independent ShapePrototype with the same type/color. solve(type, color, newColor) clones a shape, changes the clone's color to newColor, and returns both { original, clone } — proving the original is untouched.
Let a subject notify a list of subscribed observers whenever its state changes, without the subject knowing anything concrete about who's listening. Implement Subject.subscribe(name)/notify(value): every subscribed observer's update(value) should log { observer: name, value } when notified. solve(names, values) subscribes every name, then calls notify for every value, and returns the full log. solve(['a', 'b'], [1]) → [{ observer: 'a', value: 1 }, { observer: 'b', value: 1 }]
Defer object creation to a factory function so calling code depends on an interface, never on concrete classes. Implement createNotification(type), returning an EmailNotification, SMSNotification or PushNotification — each with a send(message) method that formats the message differently. solve(type, message) creates the right notification and returns send(message). solve('sms', 'Hi') → '[SMS] Hi'
Provide a single, simplified entry point in front of a set of complex subsystems, so callers don't need to know the CPU, Memory and Disk have to be driven in a specific order. Implement ComputerFacade.start()/shutdown(), each calling the three subsystems (CPU, Memory, Disk) in the right order and collecting their return values. solve(action) builds a ComputerFacade and calls start() or shutdown().
Construct a complex object step by step through a fluent, chainable API instead of one giant constructor call. Implement PizzaBuilder: setSize(size) and addTopping(topping) each return this so calls chain, and build() returns the final { size, toppings }. solve(steps) replays a list of { op: 'setSize' | 'addTopping', value } steps against a fresh builder and returns the built pizza.
Make an existing class's incompatible interface work with the interface client code expects, without modifying the original class. OldLogger only has logMessage(msg) returning 'OLD:' + msg. The rest of the app expects a Logger with info(msg) returning 'INFO:' + msg. Implement LoggerAdapter, wrapping an OldLogger to satisfy the Logger interface. solve(messages) logs each message through the adapter and returns the results.
Design a small animal hierarchy using interfaces and implements: Define interface Animal with: name: string, sound(): string. Define interface Pet extends Animal adding: owner: string. Implement class Dog implements Pet with: name and owner as constructor params. sound() returns "Woof!". Implement solve(name, owner) that creates a Dog and returns { name, owner, sound: dog.sound() }.
Given a list of integers, return a new list containing the square of each number that is strictly positive, using a single list comprehension. Examples solve([1, -2, 3, 0, -4, 5]) → [1, 9, 25] solve([-1, -2, -3]) → [] solve([2, 4, 6]) → [4, 16, 36] Constraints Return an empty list if no positive number is found. Do not mutate the input. Your implementation must use a list comprehension (single expression, no for loop body). In JavaScript / TypeScript — Chain filter and map in a single expression — JavaScript's equivalent of a list comprehension. Do not mutate the input.
The Result pattern replaces thrown exceptions with typed return values. It's a discriminated union — a union type with a shared field (ok) that TypeScript uses to narrow to the correct variant: | Function | Signature | Description | |----------|-----------|-------------| | ok(value) | <T>(v: T) → Ok<T> | Wraps a successful value | | err(error) | <E>(e: E) → Err<E> | Wraps an error | | unwrapOr(result, default) | <T,E>(r: Result<T,E>, d: T) → T | Returns value or default | | isOk(result) | <T,E>(r: Result<T,E>) → r is Ok<T> | Type guard for success | The isOk return type result is Ok<T> is a type guard — calling it narrows result to the success branch.
Implement composeEventHandlers(...handlers) that: Returns a single function that calls each provided handler in order. Skips null/undefined handlers. Stops calling subsequent handlers if any handler calls event.stopPropagation() (check event.isPropagationStopped?.()). solve(handlerCount, stopAt, callCount) builds composed handlers and returns which indices were called when triggered.
Implement solve(events, wait) that simulates leading-edge throttle over a fixed timeline. events is a sorted list of [timestampMs, value] pairs representing invocations of a throttled function. Return all [emitTimeMs, value] pairs the throttled function would actually fire. Semantics (leading edge) The first call in each window fires immediately at its own timestamp. Any call within wait ms of the last fired call is suppressed. After the window expires the next call fires. Example solve([[0,'a'],[50,'b'],[200,'c']], 100) → [[0,'a'],[200,'c']]
Implement solve(callArgs) that demonstrates the once higher-order function: a wrapper that ensures the underlying function runs at most once and returns the same result for every subsequent call. callArgs is an array of argument lists. The first call invokes the underlying function; all subsequent calls return the cached first result. The underlying function simply returns the sum of its arguments. Example solve([[1,2],[3,4],[5]]) → [3, 3, 3] (first call returns 1+2=3; subsequent calls also return 3)
Point-free style defines functions without explicitly mentioning their arguments. It relies on composition and partial application. Refactor the given pointful functions into point-free style using the provided helpers. All functions should produce identical results.
Memoization caches the result of a pure function so repeated calls with the same arguments are instant. Implement memoize(fn) that caches results keyed by the serialised arguments.
The Maybe monad eliminates null checks by wrapping a value that might be absent. Implement a Maybe class with: Maybe.of(value) — wraps a value .map(fn) — applies fn if value is not null/undefined, otherwise stays Nothing .getOrElse(defaultValue) — returns the value or defaultValue if Nothing
Define the skeleton of an algorithm in a base class, deferring only specific steps to subclasses — every beverage boils water and pours a cup the same way, but brewing and condiments differ. Implement Tea/Coffee, overriding only brew() and addCondiments() — Beverage.prepare() (already wired) calls them in a fixed sequence. solve('tea') → ['Boil water', 'Steep tea', 'Pour in cup', 'Add lemon']
Let an object change its behavior when its internal state changes, by delegating to a state object rather than a pile of if/switch statements — a traffic light that cycles Red → Green → Yellow → Red. Implement RedState/GreenState/YellowState, each returning the NEXT state object from next(). solve(transitions) starts at Red and calls next() that many times, returning the full sequence of state names visited (including the starting state). solve(1) → ['Red', 'Green']
Put a stand-in object in front of a real, possibly-expensive object to control access to it — here, a caching proxy that avoids re-fetching the same id twice. Implement CachingProxy.fetchData(id): return the cached result if seen before, otherwise delegate to the wrapped RealDataService (which tracks how many times it was actually called) and cache the result. solve(ids) fetches every id through the proxy and returns { results, realCallCount }. solve(['a', 'a', 'b']) → { results: ['data-a', 'data-a', 'data-b'], realCallCount: 2 } — the real service was only hit twice.
Capture and externalize an object's internal state so it can be restored later, without violating encapsulation — a text editor's undo stack. Implement Editor.save() (returns an EditorMemento snapshot of the current content) and Editor.restore(memento) (resets content back to that snapshot). solve(ops) replays { op: 'type', text } | { op: 'save' } | { op: 'undo' } steps and returns the final content — 'undo' restores the most recent save. solve([{op:'type',text:'Hello'},{op:'save'},{op:'type',text:' World'},{op:'undo'}]) → 'Hello'
Have objects communicate through a central mediator instead of referencing each other directly — a chat room, not a mesh of direct user-to-user connections. Implement ChatRoomMediator.send(from, text): deliver the message to every registered user except the sender, logging each delivery. solve(messages, users) registers each user with the mediator, sends every message, and returns the delivery log. solve([{ from: 'alice', text: 'hi' }], ['alice', 'bob']) → [{ to: 'bob', from: 'alice', text: 'hi' }]
Provide a way to access a collection's elements sequentially without exposing its internal structure — a custom Collection should be usable in a for...of loop just like a built-in array. Implement Collection[Symbol.iterator](), returning an iterator object whose next() yields { value, done } pairs over the collection's items. solve(items) adds every item to a Collection, iterates it with for...of, and returns each value doubled.
Share a single object instance across many usages of the same intrinsic state (here, a rendered character) instead of allocating a fresh one every time — a text editor shouldn't create a million distinct 'a' glyph objects for a million 'a' characters. Implement GlyphFactory.get(char): return the cached Glyph for that character, creating and caching it only the first time it's seen. solve(text) renders every character through the factory and returns { rendered, uniqueGlyphs } — the cache size proves reuse. solve('aab') → { rendered: '<a><a><b>', uniqueGlyphs: 2 } — two 'a's share one Glyph.
Attach new behavior to an object dynamically, by wrapping it, instead of subclassing every combination (CoffeeWithMilk, CoffeeWithMilkAndSugar, ...). Implement MilkDecorator/SugarDecorator, each wrapping a Coffee and adding to its cost()/description(). solve(addons) wraps a SimpleCoffee (cost 2, description 'Coffee') with one decorator per addon, in order, and returns the final { cost, description }. solve(['milk', 'sugar']) → { cost: 2.75, description: 'Coffee + Milk + Sugar' }
Treat individual objects and compositions of objects uniformly — a file and a folder full of files should answer getSize() the same way, recursively. Implement FileNode.getSize() (returns its own size) and FolderNode.getSize()/add() (sums its children's sizes, recursively). solve(tree) builds a tree from nested numbers/arrays (5 = a file of size 5, [a, b] = a folder containing a and b) and returns the total size. solve([1, [2, 3], [4, [5, 6]]]) → 21
Encapsulate a request as an object so it can be queued, logged, or undone — instead of calling light.turnOn() directly, you execute a LightOnCommand. Implement LightOnCommand/LightOffCommand (execute()/undo()) and the solve history stack that supports 'undo'. solve(ops) runs a sequence of 'on' | 'off' | 'undo' against a Light, pushing every executed command onto a history stack that 'undo' pops and reverses, and returns { isOn, historyLength }.
Pass a request along a chain of handlers until one of them handles it — the sender doesn't know (or care) which handler will actually process it. Implement Handler.handle(level): if this handler can handle the level, return its own name; otherwise forward to the next handler in the chain, or return 'unhandled' if there isn't one. solve(levels) builds an InfoHandler → WarnHandler → ErrorHandler chain and runs every level through it. solve(['error', 'info']) → ['ErrorHandler', 'InfoHandler']
Decouple an abstraction (Circle) from its implementation (how it's rendered) so the two can vary independently — a Circle doesn't need a VectorCircle/RasterCircle subclass for every renderer. Implement VectorRenderer/RasterRenderer (renderCircle(radius)) and Circle.draw(), which delegates to whichever renderer it was given. solve('raster', 5) → 'raster-circle(5)'
Produce families of related objects — a light-theme button always pairs with a light-theme checkbox, never a dark one — without the caller ever naming a concrete class. Implement LightFactory/DarkFactory, each creating a matching Button + Checkbox pair, and getFactory(theme) to pick the right family. solve('dark') → { button: 'dark-button', checkbox: 'dark-checkbox' }
Implement solve(lists, n) that: Chains all sub-lists into a single sequence with itertools.chain.from_iterable. Returns the first n elements that are even, as a list. Use itertools.islice to avoid materializing the full sequence. Examples solve([[1,2,3],[4,5,6],[7,8]], 3) → [2, 4, 6] solve([[1,3,5],[7,9]], 2) → [] (no evens) solve([[2,4,6,8]], 2) → [2, 4] Constraints Do not use a plain for-loop to collect results — use itertools combinators. In JavaScript / TypeScript — Implement the lazy helpers of the starter as generator functions — chain, filterLazy and take — so nothing past the n-th even number is ever read.
Implement solve(numbers) using only functools.reduce (no loops) that returns the product of all elements in numbers. If numbers is empty, return 1 (identity element for multiplication). Examples solve([1, 2, 3, 4]) → 24 solve([5]) → 5 solve([]) → 1 solve([2, 0, 5]) → 0 Constraints Must use functools.reduce. No explicit for/while loops. In JavaScript / TypeScript — Use Array.prototype.reduce with an initial value of 1 — no loops.
Build a small class hierarchy, then answer one question about it per call. solve(aspect) returns { aspect: value } — a single-key dict naming what was asked. The grader compares JSON, so it asks for one aspect at a time rather than for the classes themselves. The hierarchy Shape (base class) Constructor: __init__(self, color: str) Method: describe(self) -> str returns "I am a {color} shape." Circle (extends Shape) Constructor: __init__(self, color: str, radius: float) Method: area(self) -> float returns π × radius² (use math.pi) Overrides describe(self) → "I am a {color} circle with radius {radius}." Rectangle (extends Shape) Constructor: __init__(self, color: str, width: float, height: float) Method: area(self) -> float returns width × height Overrides describe(self) → "I am a {color} rectangle of {width}x{height}." The aspects | aspect | value to return | |---|---| | shape_describe | Shape("blue").describe() | | circle_describe | Circle("red", 5).describe() | | circle_area_approx | Circle("red", 5).area() rounded to 2 decimals | | circle_area_unit | Circle("red", 1).area() | | rectangle_describe | Rectangle("green", 4, 6).describe() | | rectangle_area | Rectangle("green", 4, 6).area() | | circle_is_shape | whether a Circle is an instance of Shape | | rectangle_is_shape | whether a Rectangle is an instance of Shape | Example In JavaScript / TypeScript — Use class … extends Shape and super(color); the area is Math.PI radius 2, rounded to two decimals for circle_area_approx, and instanceof answers the _is_shape aspects.
TypeScript decorators are functions that wrap and augment classes, methods, or properties: The same pattern works as plain higher-order functions — which is the approach here (Judge0 doesn't need the @ syntax). Implement three method-wrapping utilities: | Utility | Behaviour | |---|---| | memoizeMethod(fn) | Cache results by stringified args — same args → same result, no re-execution | | logMethod(fn, name) | Push "[name] called with [args]" to callLog before each call | | retryMethod(fn, n) | Retry up to n times on error before re-throwing |
Generics allow a class to work with different types while maintaining full type safety. Without generics, a Stack would accept and return any: | Method | Return type | Behavior | |--------|------------|----------| | push(item) | number | Adds item, returns new size | | pop() | T \| undefined | Removes and returns top | | peek() | T \| undefined | Returns top without removing | | size() | number | Current item count | | isEmpty() | boolean | True when size is 0 | | toArray() | T[] | Copy: bottom to top |
A discriminated union is a union where every member has a shared literal property (the discriminant). TypeScript uses it to narrow types in a switch: The never type represents impossible values. If you add a new shape variant and forget to handle it, TypeScript catches it via assertNever: | Shape | Formula | |-------|---------| | square | side² | | rectangle | width × height | | triangle | (base × height) / 2 | | rhombus | (d1 × d2) / 2 |
Branded types (also called nominal types) allow you to distinguish values that have the same runtime type but different semantic meaning: The _brand field is a phantom type — it only exists at compile time. At runtime, the value is just a string. Instead of isSlug(x): boolean, return the branded type or throw:
Implement a form reducer handling these actions: { type: 'change', field, value } — update single field. { type: 'reset' } — back to initial. { type: 'submit' } — set submitted: true. State shape: { values: {...}, submitted: boolean }. solve(initialValues, actions) returns final state.
Implement an EventBus class with: on(event, handler) — subscribe. Returns an unsubscribe function. emit(event, ...args) — call all handlers for the event. off(event, handler) — remove a specific handler. solve(ops) replays operations and returns all emissions captured.
Implement pipe(...fns) and compose(...fns) that chain unary functions together. pipe(f, g, h)(x) applies left-to-right: h(g(f(x))) compose(f, g, h)(x) applies right-to-left: f(g(h(x))) solve(value, fns, mode) returns the result of piping or composing a list of math functions over value. mode is 'pipe' or 'compose'.