Interview Prep
Problems drawn from real interviews at product companies. Solve them in JavaScript, TypeScript, Python or SQL.
Difficulty
Topics
Language
303 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)
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() }.
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.
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'] }
Implement solve(serializedFn, expectedMsg?) where serializedFn is the stringified source of a zero-argument function — and your code must reconstruct and call it to observe whether it throws. solve must handle these cases: | Call | Behaviour | |---|---| | solve(fn, undefined) | Passes if the function throws anything. | | solve(fn, 'msg') | Passes if the error message contains 'msg'. | | solve(fn, null) | Passes if the function does not throw. | Return { pass: boolean, message: string }.
Implement a lightweight version of Jest's expect() API. solve(matcher, received, expected?) must support these matchers: | Matcher | Description | |---|---| | 'toBe' | Strict equality (===). expected required. | | 'toEqual' | Deep equality (nested objects/arrays). expected required. | | 'toBeTruthy' | Passes if received is truthy. | | 'toBeFalsy' | Passes if received is falsy. | | 'toBeNull' | Passes if received === null. | | 'toBeUndefined' | Passes if received === undefined. | | 'toBeDefined' | Passes if received !== undefined. | Return an object { pass: boolean, message: string }. When passing, message is ''. When failing, message is a human-readable explanation, e.g.: "Expected 5 but received 3" "Expected value to be truthy but received false"
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
TypeScript uses control flow analysis to narrow types within branches. Implement describe(value) that returns a string describing its input: | Input | Output | |---|---| | string | "string: {value}" | | number (non-NaN) | "number: {value}" | | Date instance | "date: {value.toISOString()}" | | object with name property | "named: {value.name}" | | anything else | "unknown" | Order matters — check from most specific to least specific. Examples describe("hello") → "string: hello" describe(42) → "number: 42" describe(new Date("2024-01-01")) → "date: 2024-01-01T00:00:00.000Z" describe({ name: "Alice" }) → "named: Alice"
Implement three generic utilities: identity<T>(value: T): T — returns the value as-is. first<T>(arr: T[]): T | null — returns the first element, or null for empty arrays. merge<A, B>(a: A, b: B): A & B — shallow-merges two objects. Then implement solve(fn, ...args) that dispatches to the right function. Examples identity(42) → 42 first([1, 2, 3]) → 1 first([]) → null merge({ a: 1 }, { b: 2 }) → { a: 1, b: 2 }
Add explicit TypeScript annotations to make the following functions type-safe. Implement three functions: greet(name) — accepts a string, returns a string greeting "Hello, {name}!". add(a, b) — accepts two numbers, returns their number sum. getFullName(user) — accepts an object with firstName: string and lastName: string, returns a string "{firstName} {lastName}". Examples greet("Alice") → "Hello, Alice!" add(3, 4) → 7 getFullName({ firstName: "Jane", lastName: "Doe" }) → "Jane Doe"
Implement five type guard functions. In TypeScript, a type guard is a function whose return type is x is T — a type predicate that narrows the type of a variable in the surrounding scope: | Guard | Passes for | Fails for | |-------|-----------|-----------| | isString | 'hello' | 42, null | | isNumber | 42 | '42', NaN | | isArray | [], [1,2,3] | {}, null | | isNullish | null, undefined | 0, '', false | | isObject | { a: 1 } | [], null, functions | isNumber must return false for NaN (it is typeof 'number' but not a valid number) isObject must return false for arrays (they are objects but not plain objects) isObject must return false for null (typeof null === 'object' is JS's famous bug)
In TypeScript, tuple types are fixed-length arrays where each index has a specific type: The difference from a plain array: Array<string | number> — every element is string | number [string, number] — position 0 is string, position 1 is number zip: pairs each element at the same index, stops at the shorter array unzip: inverse of zip — splits pairs back into two separate arrays
watch() is Vue's API for reacting to reactive state changes with access to both the new and old values. Key behaviour: The callback is NOT called immediately — only when the source changes. The callback receives (newValue, oldValue). The source is tracked automatically — when a reactive property accessed inside the source getter changes, the watcher re-runs. Implement watch(source, callback), reactive(obj), and _reset(). The solve(op, ...args) harness is provided — do not modify it.
Implement solve(fnExprs, initial) where fnExprs is an array of arrow function expression strings and initial is the starting value. Apply each function left-to-right, passing the result of each to the next (like Unix pipes or Array.prototype.reduce). solve(['x => x + 1', 'x => x 2', 'x => x - 3'], 5) → (5 + 1) 2 - 3 → 9 Rules Compile each expression with new Function('return (' + expr + ';)()')). If fnExprs is empty, return initial unchanged.
Real throttle is hard to test deterministically with timers. Instead, implement solve(events, interval) that simulates a leading-edge throttle. events is a list of [timestampMs, value] pairs representing calls to a throttled function. Return only the [timestamp, value] pairs that would actually fire (one at most per interval window). Semantics (leading edge) The first call in a window fires immediately. Subsequent calls within the same window are dropped. A new window starts interval ms after the last fired call. solve([[0,'a'],[5,'b'],[10,'c'],[20,'d']], 15) → [[0,'a'],[20,'d']]
Hash a string with SHA-256 using Node.js's built-in crypto module. solve('hello') → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' This module is Node.js-only (no browser equivalent without a polyfill).
Node.js core uses error-first callbacks: (err, result) => void. Implement promisify(fn) that wraps such a function to return a Promise. promisify(fn)(...args) should: Resolve with result if the callback is called with (null, result). Reject with the error if the callback is called with (err). solve drives your implementation through a series of test cases and returns whether each resolved or rejected.
Parse an HTTP Authorization: Basic <base64> header into credentials. solve('Basic dXNlcjpwYXNz') → { username: 'user', password: 'pass' } Use Buffer.from(b64, 'base64').toString('utf8') (Node.js built-in). Rules: Return null for missing/invalid prefix or missing colon. The first : separates username from password; the password may contain colons.
The function below parses a JSON string, but it will throw a SyntaxError when the input is invalid — crashing the caller with an unhandled exception. The bug: JSON.parse throws on malformed input. In a server context, one bad request body can take down your entire request handler. Your task: fix solve(jsonString, fallback) so that: It returns the parsed value on valid JSON. It returns fallback (never throws) on any invalid input.
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 user's browser. Your task: fix solve(str) so that every HTML special character is escaped before being returned. | Character | Escaped form | |-----------|--------------| | & | & | | < | < | | > | > | | " | " | | ' | ' | Safe strings must pass through unchanged.
Implement solve(str) that returns true when every opening bracket in str is closed by the correct matching bracket in the correct order, and false otherwise. Supported pairs: () [] {} Non-bracket characters are ignored. Examples Approach Use a stack: push each opening bracket; on a closing bracket, pop and verify the match. Return true only if the stack is empty at the end. Constraints Input may contain any Unicode characters; only ()[]{} matter. Do NOT use a counter — counters cannot detect interleaved mismatches like ([)].