Interview Prep
Problems drawn from real interviews at product companies. Solve them in JavaScript, TypeScript, Python or SQL.
Difficulty
Topics
Language
74 challenges
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.
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.
Parse a URL query string (without the leading ?) into an object. solve('a=1&b=hello&c=') → { a: '1', b: 'hello', c: '' } Decode URI components. Empty input returns {}. Skip empty pairs.
Validate and parse a port string. Returns the port number if valid (integer between 1 and 65535), else null. Reject: empty, non-numeric, non-integer, out of range, leading/trailing whitespace.
Parse an HTTP Cookie header string into an object. solve('sid=abc; theme=dark') → { sid: 'abc', theme: 'dark' }. Trim whitespace around names and values. Skip empty entries.
Parse '<protocol>://<host>:<port><path>' into an object. Port and path are optional. solve('https://example.com:8080/api') → { protocol: 'https', host: 'example.com', port: 8080, path: '/api' }. If no port: port: null. If no path: path: ''.
Given a file path, return the MIME type based on extension. Supported: .html → 'text/html' .css → 'text/css' .js → 'application/javascript' .json → 'application/json' .png → 'image/png' .jpg, .jpeg → 'image/jpeg' Unknown / no ext → 'application/octet-stream' Match is case-insensitive on the extension.
Merge multiple env-var objects into one. Later objects override earlier ones, but null values do not override existing keys (only present, non-null keys win). solve([{ A: '1' }, { A: null, B: '2' }]) → { A: '1', B: '2' }.
Join URL/POSIX path segments with / and normalize: Collapse multiple slashes. Drop empty segments (except a leading absolute marker). Preserve a leading / if the first segment starts with one. solve(['/a', 'b', '/c/']) → '/a/b/c/' (preserves trailing slash of last segment).
Map an HTTP status code to its standard text. Supported codes: 200 OK, 201 Created, 204 No Content, 301 Moved Permanently, 302 Found, 304 Not Modified, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable. Unknown code → 'Unknown'.
Convert an array of bytes (0–255) into a lowercase hex string. solve([255, 16, 0]) → 'ff1000'. Pad each byte to 2 hex chars.
Convert a byte count into a human-readable string using binary units (1024-based): B, KB, MB, GB, TB. solve(1536) → '1.5 KB'. Round to 1 decimal place. solve(0) → '0 B'.
The function below builds a file path by concatenating a base directory with user-supplied input. The bug: an attacker can pass "../../../etc/passwd" as the filename and read any file on the server — a classic path traversal attack. Your task: fix solve(base_path, user_input) so that: Valid filenames within the base directory return the full, normalized path. Any input that would resolve outside base_path returns None.
Implement compose(middlewares) that chains async middleware functions (like Koa.js). Each middleware receives (req, next) where next() calls the following middleware. solve creates middlewares from a declarative ops array and runs them: {op:'set', key, val} — adds key to req then calls next() {op:'stop'} — marks req.stopped = true, does not call next() Return the final req state after compose(middlewares)(req) resolves.
Implement createEventBus() with: subscribe(event, listener) — register a listener, returns an unsubscribe function. publish(event, data) — call all listeners for the event with data. solve drives your bus through a sequence of operations and returns the call log: [[subscriberId, data], ...]. Operations: ['subscribe', event, id] — subscribe listener id to event ['publish', event, data] — publish data to event ['unsubscribe', id] — call the unsubscribe function returned for id
Implement createContainer() with: register(token, factory, { singleton }) — register a provider. singleton: true: factory called once; subsequent resolves return the cached value. singleton: false (default): factory called every time resolve is called. resolve(token) — instantiate and return the value. solve registers tokens backed by an auto-incrementing counter factory and returns an array of resolved values. Singleton tokens return the same counter value; transient tokens return a new one each time.
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
The function below builds a file path by concatenating a base directory with user-supplied input. The bug: an attacker can pass "../../../etc/passwd" as the filename and read any file on the server — a classic path traversal (directory traversal) attack. Your task: fix solve(basePath, userInput) so that: Valid filenames within the base directory return the full path. Any input that would escape basePath returns null. Use string operations only (no require('path') available in this sandbox).
The function below returns whatever URL the caller passes in, unchanged — the exact value a login page might feed straight into res.redirect(). The bug: an attacker can craft a link like https://codejump.io/login?next=https://evil.com and, after a successful login, the victim gets redirected to a phishing site that looks just like yours. Your task: fix solve(redirectUrl) so that: A relative path starting with a single / is returned unchanged (safe — stays on this site). Anything else — an absolute URL, a javascript: URL, or a protocol-relative URL starting with // (a common bypass, since browsers treat it as same-protocol-different-host) — falls back to '/'.
The function below validates a username using a regular expression with a nested quantifier: /^([a-z0-9_]+)+$/. The bug: this pattern is vulnerable to Regular Expression Denial of Service (ReDoS). For certain crafted inputs, the regex engine's backtracking takes exponential time — a string of ~25 valid characters followed by one invalid character can freeze the event loop for seconds, blocking every other request on the server. Your task: fix solve(username) so it validates the same rule — 3 to 20 lowercase letters, digits, or underscores — without the vulnerable nested-quantifier pattern.
The function below builds a MongoDB query filter by embedding user input directly into a $where clause string. The bug: $where runs its string as raw JavaScript on the database server. An attacker can pass "0'; while(true){}" as the threshold and hang the database with an infinite loop, or pass a boolean-injection payload to bypass the filter entirely and match every document. This is the real query from OWASP's NodeGoat (a deliberately-vulnerable Node.js app used for security training): Your task: fix solve(userId, threshold) so it returns a filter object built from standard MongoDB query operators instead of an interpolated $where string:
The function below updates a user's benefits start date, taking the target userId directly from the request with no check on who is making the request. The bug: this is the real behavior of OWASP's NodeGoat (a deliberately-vulnerable Node.js app used for security training) — its benefits-update endpoint lets any logged-in user change any other user's benefits, simply by sending a request with a different userId. There is no check that the requester is an admin or is updating their own record. Your task: fix solve(requestingUser, targetUserId, newBenefitDate) so the update is only allowed when: the requesting user is an admin, OR the requesting user is updating their own record (requestingUser.id === targetUserId) Otherwise, return { allowed: false }.
The function below returns every memo in the system, regardless of who is asking. The bug: this is the real behavior of OWASP's NodeGoat (a deliberately-vulnerable Node.js app used for security training) — its memo-listing endpoint fetches all memos from the database and renders them, without ever filtering by the logged-in user. Any authenticated user can read every other user's private memos just by visiting the page. Your task: fix solve(allMemos, currentUserId) so it returns only the memos that belong to currentUserId.
The function below builds a SQL query by embedding user input directly into the string. The bug: an attacker can pass "1' OR '1'='1" as the id parameter and dump the entire users table (or worse, DROP TABLE users). Your task: fix solve(id) so it returns a parameterized query object instead of a raw string: The sql string must use the $1 placeholder. The user input must only appear in the params array — never embedded in the SQL string.
Build a URL from components: { protocol, host, port?, path?, query? }. Always include protocol://host. Port appended only if present. Path defaults to '/'. Query: object → URL-encoded query string with sorted keys; skip keys whose value is null.
Implement a token bucket: capacity C, refill R tokens per ms. consume(now, n) first refills based on elapsed time (capped at C), then consumes n tokens if available; returns true/false. solve(capacity, refillPerMs, requests): requests are [time, n], returns array of allow/deny.
Inverse of querystring.parse. Convert an object into a URL-encoded query string. URL-encode keys and values. Skip keys whose value is null. Sort keys alphabetically for deterministic output. solve({ a: 1, b: 'hi there' }) → 'a=1&b=hi%20there'.
Simulate consuming a readable stream that emits chunks (string arrays). Return one concatenated string. solve(['Hello, ', 'World', '!']) → 'Hello, World!'.
Match URL paths against route patterns with :param placeholders. solve(routes, path) returns { pattern, params } for the first matching route, or null if none. E.g. routes ['/users/:id', '/posts/:slug/comments/:cid'], path /users/42 → { pattern: '/users/:id', params: { id: '42' } }.
Implement a semaphore limiting concurrent async ops to max. solve(max, durations) simulates each duration with setTimeout under the semaphore. Returns total elapsed time (ms) across all tasks.
solve(scenarios, maxAttempts) retries an async fn up to maxAttempts times. Returns the result. If all attempts fail, throws the last error. scenarios = an array of strings: 'fail' or 'ok:<value>'. The fn consumes one scenario per call.
Parse an HTTP Range: bytes=... header. Support multiple ranges. solve('bytes=0-499', 1000) → [{ start: 0, end: 499 }]. solve('bytes=500-', 1000) → [{ start: 500, end: 999 }]. solve('bytes=-200', 1000) → [{ start: 800, end: 999 }] (last 200 bytes). solve('bytes=0-99,200-299', 1000) → two ranges. Invalid header → null.
Resolve . and .. segments in a POSIX path. Preserve leading /. solve('/a/b/../c/./d') → '/a/c/d'. solve('a/b/../../c') → 'c'. Going above root collapses to / (or empty for relative).
Parse a JSONL (newline-separated JSON) string. Skip empty lines. Throw on invalid JSON. solve('{"a":1}\n{"b":2}') → [{ a: 1 }, { b: 2 }].
Normalize header names to lowercase. If a header appears multiple times, join its values with ', ' (in original order). solve([['Content-Type', 'text/html'], ['Set-Cookie', 'a=1'], ['set-cookie', 'b=2']]) → { 'content-type': 'text/html', 'set-cookie': 'a=1, b=2' }.
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`.
Generate a weak ETag from a string body: 'W/"<length>-<hash>"' where: length is the body byte length (string length). hash is a simple 32-bit FNV-1a hash, formatted as lowercase hex. solve('hello') returns 'W/"5-4f9f2cab"'.
Validate process.env-like object against a schema. Schema: { key: { type: 'string'|'number'|'boolean', required: bool, default? } }. string: kept as-is. number: parse with Number(), fail if NaN. boolean: 'true'/'1' → true; 'false'/'0' → false; else fail. required + missing → throw with message 'Missing: <key>'. Apply defaults if missing. solve(schema, env) returns parsed object, or throws.
Given a CORS config and an incoming request, return whether it's allowed and the response headers to send. Config: { origins: string[] | '', methods: string[], headers: string[] }. Request: { origin, method, headers }. Returns { allowed, responseHeaders }. If origin matches '' or is in origins, method is in methods, and every requested header is in allowed headers (case-insensitive), it's allowed.
Build a Set-Cookie header value from name, value, and options. Options support: maxAge (number, seconds), httpOnly (bool), secure (bool), sameSite ('Strict' | 'Lax' | 'None'), path (string). Order: name=value; Max-Age=N; Path=/; HttpOnly; Secure; SameSite=Lax (only include flags that are truthy / present).
Encode an array of bytes (0–255) into a Base64 string. solve([77, 97, 110]) → 'TWFu'. Pad with = to a length multiple of 4.
Decode a Base64 string into an array of bytes. solve('TWFu') → [77, 97, 110]. Trailing = indicates padding. Input is always valid base64.
Generate exponential backoff delays with optional jitter cap. solve(base, attempts, max) returns array of min(base * 2^i, max) for i in 0..attempts-1.
Parse an Authorization header. Supports: Bearer <token> → { scheme: 'Bearer', token } Basic <base64> → decode to user:pass and return { scheme: 'Basic', user, pass } For Basic, you may use the global atob. Invalid format → null.
The token bucket algorithm allows bursts while enforcing an average rate. Implement createTokenBucket(capacity, refillPerMs): capacity — maximum tokens the bucket can hold. refillPerMs — tokens added per millisecond (can be fractional). consume(tokens, timestamp) — attempt to consume tokens at virtual time timestamp. Returns true if allowed (enough tokens), false otherwise. The bucket starts full (capacity tokens). Tokens are added based on elapsed time since the last call: Math.min(capacity, current + elapsed * refillPerMs).
Implement a minimal synchronous Observable class: Observable.of(...values) — static factory from a list of values. obs.map(fn) — returns a new Observable applying fn to each value. obs.filter(fn) — returns a new Observable keeping values where fn is truthy. obs.take(n) — returns a new Observable with at most n values. obs.subscribe({ next }) — executes the chain synchronously, calling next for each value. solve applies a declarative operator chain to a values array. Operators: ['map', expr], ['filter', expr], ['take', n] where expr is an arrow function string compiled with new Function.