Interview Prep
Problems drawn from real interviews at product companies. Solve them in JavaScript, TypeScript, Python or SQL.
Difficulty
Topics
Language
82 challenges
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 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'
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 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']}
Implement solve(n) that returns a list of strings for numbers from 1 to n (inclusive) following these rules: "FizzBuzz" if divisible by both 3 and 5 "Fizz" if divisible by 3 only "Buzz" if divisible by 5 only The number as a string otherwise Raise ValueError("n must be positive") if n < 1. Examples solve(5) → ["1", "2", "Fizz", "4", "Buzz"] solve(15)[-1] → "FizzBuzz"
Implement solve(a, b) that: Returns a / b (float division) if b != 0. Returns None if b == 0 (catch the ZeroDivisionError). Raises a TypeError with message "Inputs must be numbers" if either argument is not an int or float. Examples solve(10, 2) → 5.0 solve(7, 0) → None solve("a", 2) → raises TypeError("Inputs must be numbers")
This function does three distinct jobs: filters active users, filters seniors, and formats — a maintenance nightmare. Refactor into three functions, each with one job: filter_active(users) — keep only active users filter_seniors(users) — keep only users aged 60+ format_users(users) — format as "NAME (age)" strings solve(users, 'filter_active') dispatches to the right function.
Software entities should be open for extension, closed for modification. Adding a new case should not require editing existing code. Refactor so each shape has its own area callable. Create create_circle(r), create_rect(w, h), create_triangle(b, h). Then solve(kind, *dims) just builds the right shape and calls its area — no growing if chain needed when adding new shapes.
Defensive None 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 None. solve(None) → {'name': 'Anonymous', 'email': 'no-email@placeholder.com', 'role': 'guest'}
Method chaining creates readable, declarative code. Each method returns self to allow chaining. Implement a Pipeline class (via create_pipeline(items)) with filter(fn), map(fn), take(n) and value() methods — all chainable (each returns self except value). create_pipeline([1,-2,3,4,5]).filter(lambda x: x>0).map(lambda x: x*2).take(2).value() → [2, 6]
Subtypes must be substitutable for their base type without breaking behavior. Refactor using factory functions so create_rectangle and create_square are independent — no inheritance. Both expose an area callable. solve('square', 5) → 25 (side²) solve('rect', 4, 6) → 24 (width × height)
Deep inheritance chains are brittle. When requirements change, you end up fighting the hierarchy. Refactor using composition: create create_animal(name, sound) that returns a dict with a speak callable. No classes, no inheritance. solve(name, sound) calls create_animal(name, sound)['speak'](). solve('Rex', 'woof') → 'Rex says: woof'
High-level modules should not depend on low-level modules — both should depend on abstractions. Refactor solve(db, user_id): instead of hardcoding the repo, create a simple in-memory repo from db (a plain dict) and look up user_id. The point: solve receives its data source — it doesn't instantiate one. solve({'u1': {'id': 'u1', 'name': 'Alice'}}, 'u1') → {'id': 'u1', 'name': 'Alice'}
Implement solve(pairs) that receives a list of 2-element tuples (a, b) and returns a new list where each tuple has its elements swapped: (b, a). Also implement solve_stats(numbers) that returns a tuple (minimum, maximum, total) for a list of numbers. Since Judge0 uses a single entry point, implement solve(data, mode): If mode == "swap": data is a list of 2-tuples → return list of swapped tuples. If mode == "stats": data is a list of numbers → return (min, max, sum). Examples solve([(1, 2), (3, 4)], "swap") → [(2, 1), (4, 3)] solve([3, 1, 4, 1, 5], "stats") → (1, 5, 14)
Implement solve(data, mode) using Python's math and statistics modules: "circle_area" → data is a radius; return the area of the circle (π × r²). "hypotenuse" → data is [a, b]; return the hypotenuse of a right triangle (√(a²+b²)). "mean" → return the arithmetic mean of the list data. "median" → return the median of the list data. "variance" → return the variance of the list data. Examples solve(5, "circle_area") → 78.539... solve([3, 4], "hypotenuse") → 5.0 solve([1, 2, 3, 4, 5], "mean") → 3.0
Implement solve(data, mode) using Python's json module: "serialize" → convert the Python dict/list data to a JSON string (keys sorted alphabetically). "parse" → parse the JSON string data and return the Python object. "roundtrip" → serialize then immediately parse data and return the result (should equal the input). Examples solve({"name": "Alice", "age": 30}, "serialize") → '{"age": 30, "name": "Alice"}' solve('{"name": "Alice", "age": 30}', "parse") → {"name": "Alice", "age": 30}
Implement solve(factor, x) that creates a closure internally and uses it to return x factor. Specifically, inside solve: Define an inner function multiplier(n) that returns n factor. Return multiplier(x). This ensures you practice writing closures — the inner function must capture factor from the enclosing scope. Examples
Implement solve(a, b, op) that performs the arithmetic operation specified by op on numbers a and b. Supported operations: "+" → addition "-" → subtraction "*" → multiplication "/" → float division (raises ZeroDivisionError if b == 0) "//" → floor division "%" → modulus "" → exponentiation Raise ValueError("Unknown operator") for anything else. Examples solve(5, 3, "+") → 8 solve(5, 3, "") → 125 solve(7, 2, "//") → 3 solve(5, 3, "?") → raises ValueError
Implement solve(mode, *args, kwargs) that behaves differently based on mode: "sum" → return the sum of all positional args (numbers). Return 0 if none. "join" → join all positional args as strings with the separator from kwargs.get("sep", " "). "merge" → merge all kwargs into a single dict and return it. Examples solve("sum", 1, 2, 3, 4) → 10 solve("join", "hello", "world", sep="-") → "hello-world" solve("join", "a", "b", "c") → "a b c" solve("merge", x=1, y=2, z=3) → {"x": 1, "y": 2, "z": 3}
Implement solve(sentence) that: Strips leading/trailing whitespace from the sentence. Capitalizes the first letter of each word (title case). Replaces every occurrence of the word "bad" (case-insensitive) with "good". Returns the resulting string. Examples solve(" hello world ") → "Hello World" solve("this is bad") → "This Is Good" solve(" BAD habits are bad ") → "Good Habits Are Good" Constraints Words are separated by single spaces after stripping. The replacement is case-insensitive but the result must always be lowercase "good".
Implement solve(text) that returns a dictionary mapping each unique word (lowercased) to the number of times it appears in the text. Split on whitespace only. Lowercase all words before counting. Punctuation attached to words should be stripped (only .,!?;: at start/end of each word). Examples solve("Hello hello HELLO") → {"hello": 3} solve("the cat sat on the mat") → {"the": 2, "cat": 1, "sat": 1, "on": 1, "mat": 1} solve("") → {}
Implement solve(fn_exprs, initial) where fn_exprs is a list of lambda 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 functools.reduce). solve(['lambda x: x + 1', 'lambda x: x 2', 'lambda x: x - 3'], 5) → ((5 + 1) 2) - 3 → 9 Rules Compile each expression string with eval(expr). If fn_exprs is empty, return initial unchanged.
Implement solve(text, mode) using Python's re module: "emails" → return a list of all email addresses found in text. "words_starting_f" → return a list of all words starting with f (lowercase only). "deduplicate" → return the text with any consecutive duplicate words replaced by a single instance (e.g. "the the cat" → "the cat"). Examples solve("Contact alice@x.com or bob@y.org for help", "emails") → ["alice@x.com", "bob@y.org"] solve("which foot or hand fell fastest", "words_starting_f") → ["foot", "fell", "fastest"] solve("cat in the the hat", "deduplicate") → "cat in the hat"
Implement solve(calls) that: Defines a count_calls decorator that tracks how many times the wrapped function has been called via a call_count attribute on the wrapper. Applies the decorator to a dummy function. Calls that function calls times. Returns call_count. Constraints call_count must start at 0 before the first call. The wrapper must accept any positional and keyword arguments (args, *kwargs).
Implement solve(a, b, op) where a and b are lists of values and op is one of: "union" → elements in a or b (or both) "intersection" → elements in both a and b "difference" → elements in a but not in b "symmetric_difference" → elements in a or b but not both Return the result as a sorted list (so the output is deterministic). Examples solve([1,2,3], [2,3,4], "union") → [1, 2, 3, 4] solve([1,2,3], [2,3,4], "intersection") → [2, 3] solve([1,2,3], [2,3,4], "difference") → [1] solve([1,2,3], [2,3,4], "symmetric_difference") → [1, 4]
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).
Singleton ensures a class has exactly one instance, shared everywhere it's constructed. Implement ConfigManager so that every ConfigManager() call returns the same object, using __new__ to intercept instantiation (the idiomatic Python way — no need for a separate get_instance() method). solve(operations) drives it through 'set' / 'get' / 'identity_check' ops and returns the results.
Factory Method lets a function decide which class to instantiate based on an input, instead of the caller hardcoding a specific class. Implement create_notifier(kind) returning the right Notifier subclass for 'email', 'sms' or 'push' (raise ValueError for anything else). Each notifier's send(message) returns a tagged string. solve(requests) takes a list of (kind, message) pairs and returns the sent notification for each. solve([('email', 'Welcome!')]) → ['[EMAIL] Welcome!']
Facade gives a simple, single entry point in front of a complex subsystem, so callers don't need to know how the pieces fit together. CPU, Memory and HardDrive each expose low-level operations. Implement ComputerFacade.start() to sequence the correct boot steps and return them, in order, as a list. solve(n) calls start() n times and returns each resulting sequence (verifying the facade is deterministic and repeatable).
Builder constructs a complex object step by step via a fluent interface, instead of one constructor call with a dozen parameters. Implement PizzaBuilder with set_size(size), add_topping(topping) (both return self for chaining) and build() returning the finished Pizza. Pizza.describe() returns a human-readable summary. solve(orders) builds one pizza per order dict {'size':..., 'toppings': [...]} and returns each describe() string.
Adapter wraps an incompatible interface so it matches the one your code expects, without touching the original class. LegacyPrinter.old_print(text) is what you have. Your code expects a ModernPrinter-shaped .print_text(text). Implement LegacyPrinterAdapter to bridge the two. solve(texts) prints every text through the adapter and returns the results.
Abstract Factory produces families of related objects that must stay consistent with each other — e.g. all the widgets for one UI theme. Implement get_factory(theme) returning a LightFactory or DarkFactory for 'light'/'dark' (raise ValueError otherwise). Each factory has create_button() and create_checkbox(), both .render()-able. solve(themes) returns [button.render(), checkbox.render()] for each theme. solve(['light']) → [['light-button', 'light-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.
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.
Implement solve() that returns a tuple of three classes: (Shape, Circle, Rectangle). Requirements: 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}." Example
The function below generates "unique" tokens using random.randint, producing short, low-entropy decimal strings instead of proper session tokens. The bug: Python's random module uses the Mersenne Twister — fast, but not cryptographically secure. Its output is predictable if an attacker observes enough samples, and it isn't designed to produce fixed-length, uniformly-distributed tokens. For anything security-sensitive (session IDs, password reset tokens, API keys), this is the wrong tool. Your task: fix solve(n) to generate n tokens using the secrets module — Python's cryptographically secure random generator — and return a summary of their properties.
The function below wraps user comments in a <div> with no escaping at all. The bug: a comment containing <script>alert(document.cookie)</script> gets rendered as actual HTML — the attacker's JavaScript runs in every visitor's browser, with access to their session cookie. Your task: fix solve(comments) so every comment is HTML-escaped before being wrapped, using Python's built-in html module. solve(['hello']) → ['<div>hello</div>'] solve(['<script>alert(1)</script>']) → ['<div><script>alert(1)</script></div>']
Hash a string with SHA-256 using Python's built-in hashlib module. solve('hello') → '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
The function below accepts any non-empty string as a valid email address. The bug: invalid emails like "notanemail" or "@missing.com" are accepted, causing downstream failures (bounced sends, database constraint violations, user frustration). Your task: fix solve(email) to return True only for structurally valid email addresses. A valid email for this exercise must: Have a non-empty local part (before @) Contain exactly one @ character Have a domain with at least one dot and a non-empty TLD
Real throttling 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']]
Some APIs use error-first callbacks: callback(err, result=None). Implement promisify(fn) that wraps such a function into one you can await, using an asyncio.Future — the same idea as Node's util.promisify. await promisify(fn)(...args) should: Resolve with result if the callback is invoked as callback(None, result). Raise the error if the callback is invoked as callback(err). solve drives your implementation through a series of test cases and returns whether each resolved or raised.
Callback-passing style makes async code impossible to follow — a pyramid of doom. Refactor solve(user_data, orders_data, products_data) using async/await so the function is flat and readable. Keep solve itself a regular (non-async) function — drive the coroutine internally with asyncio.run(...). solve({'id': 'u1', 'name': 'Alice'}, [{'id': 'o1'}], [{'id': 'p1'}]) → {'user': ..., 'orders': ..., 'products': ...}
Implement solve(start, stop, step) as a generator function that yields integers from start up to (but not including) stop, incrementing by step each time. Examples list(solve(0, 10, 2)) → [0, 2, 4, 6, 8] list(solve(1, 6, 1)) → [1, 2, 3, 4, 5] list(solve(5, 0, -1)) → [5, 4, 3, 2, 1] list(solve(0, 0, 1)) → [] Constraints step may be negative (count down). If step == 0, raise a ValueError. Do not use Python's built-in range(). Your function must use the yield keyword.
Parse an HTTP Authorization: Basic <base64> header into credentials. solve('Basic dXNlcjpwYXNz') → {'username': 'user', 'password': 'pass'} Use Python's built-in base64 module. Rules: Return None for a missing/invalid prefix, missing colon, or invalid base64. The first : separates username from password; the password may contain colons.