Interview Prep
Problems drawn from real interviews at product companies. Solve them in JavaScript, TypeScript, Python or SQL.
Recommended next
Easy · Functional Programming · Partial Application · ~10 min
Difficulty
Topics
Language
444 challenges
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.
A password-reset token generator is meant to build a token from a caller-supplied source of cryptographically-random integers. The bug: the buggy version ignores the secure source entirely and calls Math.random() instead — Math.random is a fast, statistically-predictable PRNG never intended for security purposes; tokens generated from it can be predicted or brute-forced far more easily than a real attacker should need. Your task: fix solve(randomInts) so it builds the token from the provided randomInts array (mapping each integer to a character via % alphabet.length), never by generating its own randomness internally.
The signup form below accepts any string as an email address without validation. The bug: malformed input flows downstream into emails, database records and third-party APIs — at best causing bounced emails, at worst becoming an injection vector in systems that trust the "email" field is actually shaped like one. Your task: fix solve(email) to return true only for strings shaped like local@domain.tld (non-empty local part, @, non-empty domain, a dot, non-empty TLD) — false otherwise.
The function below builds a SQL query by concatenating user input directly into the string. The bug: an attacker can pass admin' OR '1'='1 as the username and the resulting query becomes SELECT * FROM users WHERE username = 'admin' OR '1'='1' — always true, bypassing authentication entirely. Your task: fix solve(username) so it returns a parameterized query — a placeholder (?) in the SQL string, with the user input passed separately as a bind parameter, never concatenated.
The file-download endpoint below serves whatever path the client asks for, relative to an uploads folder. The bug: a request for ../../etc/passwd (or an absolute path like /etc/passwd) escapes the intended uploads directory entirely, exposing arbitrary files on the server. Your task: fix solve(userPath) so it returns the path unchanged when it's a safe relative path, or null when it contains .. or starts with /.
A post-login "redirect back to where you came from" feature takes a returnTo URL from the query string and redirects there unchecked. The bug: ?returnTo=https://evil-lookalike.com/login sends a freshly-authenticated user straight to a phishing page — and because the redirect started on the real, trusted domain, it's far more convincing than a cold phishing link. Protocol-relative URLs (//evil.com) are an easy-to-miss bypass of a naive check too. Your task: fix solve(url) so it returns url unchanged only when it's a same-site relative path (starts with / but not //) — otherwise it returns the safe fallback '/'.
The function below renders user-controlled content directly into an HTML string. The bug: an attacker can inject <script>alert('XSS')</script> as the argument and execute arbitrary JavaScript in the victim's browser — Cross-Site Scripting, one of the most common web vulnerabilities. Your task: fix solve(str) so every HTML special character is escaped before being returned. | Character | Escaped form | |-----------|--------------| | & | & | | < | < | | > | > | | " | " | | ' | ' | Safe strings must pass through unchanged.
Magic numbers make code unreadable. What does 86400000 mean? What about 0.2? Refactor by introducing named constants so the intent is self-documenting. The output must remain identical.
Functions with many parameters are hard to call correctly — which order do the args go? Refactor so solve accepts a single options object instead of 5 positional arguments. solve({ firstName: 'Alice', lastName: 'Smith', age: 30, email: 'a@b.com', role: 'admin' })
Silently swallowing errors hides bugs and makes debugging a nightmare. Refactor solve(json) so failure is never silent: on success return { ok: true, value: <parsed> }, on failure return { ok: false, error: 'Invalid JSON' } — never a bare null that hides which case happened.
The function below does too many things at once — it validates, transforms and formats all in one block. Refactor solve(users) by extracting helper functions so each function does exactly one thing. The output must be identical. Your extracted version should have at minimum: isValid(user), formatUser(user), and the main solve(users) that composes them.
Complex boolean expressions in if statements hide intent. Refactor by extracting the condition into a named predicate isEligible(user). solve(user, 'isEligible') → true/false · solve(user, 'getStatus') → 'eligible'/'not-eligible'
Deep nesting is hard to read. Replace nested if blocks with guard clauses that return early. Refactor using guard clauses so the happy path is at the end with minimal nesting.
The functions below share ~80% of their logic. Any change to the formatting must be made in 3 places. Refactor by extracting a generic formatCurrency(amount, symbol) helper. Then solve(amount, type) dispatches to the right formatter — 'formatUSD', 'formatEUR', or 'formatGBP'.
A function should either do something (command) or answer something (query) — never both. Refactor into two separate functions: push(stack, value) — command: pushes value, returns null/void size(stack) — query: returns the current length, no mutation solve(stack, value, 'push') dispatches to push · solve(stack, 'size') dispatches to size.
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.
A boolean flag argument is a code smell — it means the function does two things. Split this into two functions: createUser(name) and createAdminUser(name). Then solve(name, type) dispatches to the right one — no boolean flag. solve('Alice', 'createUser') → { name: 'Alice', role: 'user', permissions: ['read'] } solve('Bob', 'createAdminUser') → { name: 'Bob', role: 'admin', permissions: ['read', 'write', 'delete'] }
Functions with many parameters are hard to call correctly — which order do the args go? Refactor so solve accepts a single options dict instead of 5 positional arguments. solve({'first_name': 'Alice', 'last_name': 'Smith', 'age': 30, 'email': 'a@b.com', 'role': 'admin'})
Silently swallowing errors hides bugs and makes debugging a nightmare. Refactor solve(json_str) so failure is never silent: on success return {'ok': True, 'value': <parsed>}, on failure return {'ok': False, 'error': 'Invalid JSON'} — never a bare None that hides which case happened.
The function below does too many things at once — it validates, transforms and collects all in one loop. Refactor solve(users) by extracting helper functions so each function does exactly one thing. The output must be identical. Your extracted version should have at minimum: is_valid(user), format_user(user), and the main solve(users) that composes them.
Complex boolean expressions in if statements hide intent. Refactor by extracting the condition into a named predicate is_eligible(user). solve(user, 'is_eligible') → True/False · solve(user, 'get_status') → 'eligible'/'not-eligible'
The functions below share ~80% of their logic. Any change to the formatting must be made in 3 places. Refactor by extracting a generic format_currency(amount, symbol) helper. Then solve(amount, kind) dispatches to the right formatter — 'format_usd', 'format_eur', or 'format_gbp'.
A function should either do something (command) or answer something (query) — never both. Refactor into two separate functions: push(stack, value) — command: pushes value, returns None size(stack) — query: returns the current length, no mutation solve(stack, value, 'push') dispatches to push · solve(stack, 'size') dispatches to size.
The function below mutates its input list — a classic source of bugs. Refactor solve(cart, item) so it returns a new sorted list without modifying cart. After calling solve(cart, item), the original cart list must be unchanged.
A boolean flag argument is a code smell — it means the function does two things. Split this into two functions: create_user(name) and create_admin_user(name). Then solve(name, kind) dispatches to the right one — no boolean flag. solve('Alice', 'create_user') → {'name': 'Alice', 'role': 'user', 'permissions': ['read']} solve('Bob', 'create_admin_user') → {'name': 'Bob', 'role': 'admin', 'permissions': ['read', 'write', 'delete']}
Use literal types and union types to build a small routing helper. Implement solve(direction, speed) where: direction is one of: "north" | "south" | "east" | "west" speed is one of: "slow" | "fast" Returns a string: "{direction} at {speed} speed" Also implement isCardinal(direction) which returns true if the direction is one of the four cardinal directions, false otherwise. Examples solve("north", "fast") → "north at fast speed" isCardinal("north") → true isCardinal("diagonal") → false
Record<K, V> creates an object type with keys of type K and values of type V. Implement two functions: groupBy<T>(items: T[], key: keyof T): Record<string, T[]> Groups items by the value of key. invertRecord(rec: Record<string, string>): Record<string, string> Swaps keys and values. Examples groupBy([{lang:'js'},{lang:'ts'},{lang:'js'}], 'lang') → { js: [{lang:'js'},{lang:'js'}], ts: [{lang:'ts'}] } invertRecord({ a: '1', b: '2' }) → { '1': 'a', '2': 'b' }
Use TypeScript's built-in utility types to implement two config-update helpers. applyDefaults<T>(defaults: T, overrides: Partial<T>): T Merges overrides into defaults. Properties in overrides overwrite defaults. requireAll<T>(config: Partial<T>): config is Required<T> Returns true if every value in the config is not undefined. Examples applyDefaults({ host: 'localhost', port: 3000 }, { port: 8080 }) → { host: 'localhost', port: 8080 } requireAll({ a: 1, b: 2 }) → true requireAll({ a: 1, b: undefined }) → false