Interview Prep
Problems modelled on what technical interviews ask. Solve them in JavaScript, TypeScript, Python or SQL.
Difficulty
Topics
Language
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.
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.
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'.
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.
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.
The circuit breaker pattern prevents cascading failures by "tripping" after consecutive errors and blocking calls while the downstream service recovers. Implement createCircuitBreaker(threshold, cooldown): threshold — consecutive failures to open the circuit. cooldown — number of blocked calls before a test call is allowed. States: CLOSED — pass calls through; count consecutive failures. OPEN — block calls; count blocked calls. After cooldown blocked calls → HALF_OPEN. HALF_OPEN — let one test call through: Success → CLOSED (reset failure counter). Failure → OPEN (reset cooldown counter). cb.call(fn) — fn is () => result (throws on failure). Returns the fn result, or throws 'CIRCUIT_OPEN' when blocked.
Parse a WebSocket frame header from a byte array. Returns { fin, opcode, masked, payloadLength, headerSize }. byte0: fin = bit7, opcode = low 4 bits. byte1: masked = bit7, len = low 7 bits. If len === 126, next 2 bytes are uint16 length. If len === 127, next 8 bytes are uint64 length (we'll only need ≤ 2^32-1). If masked, add 4 mask bytes to header size.
Schedule N tasks (each with a duration) across W workers. Each worker is single-threaded; pick the worker that becomes free earliest. solve(workers, durations) returns total elapsed time (max worker time).
Build a trie-based router. solve(routes, queries) registers each route then runs each query, returning an array of { handler, params } | null. Routes are ['GET /users/:id', 'POST /posts/:slug/comments'] (method + path). Queries are 'GET /users/42'. handler is the original route string.
Process an outbox: events are [id, payload]. The processor may fail (returns false). On failure, retry up to maxRetries times. Each event must be delivered exactly once in id order. Skip events permanently failed. solve(events, attemptsByEventId, maxRetries) where attemptsByEventId maps event id → number of attempts before success (or > maxRetries to fail). Returns { delivered: [id, ...], dropped: [id, ...] }.
Topological sort. Input: { a: ['b', 'c'], b: ['c'], c: [] } means "a depends on b and c, b on c". Return tasks in execution order (dependencies first). Tie-break alphabetically. On cycle, return null.
Given input events [time, value], emit at most one event per window ms. Always emit the first event. Subsequent events within the window are dropped. solve(window, events) returns the emitted values in order.
Implement pipeline(source, transforms) where: source is an array of values. Each transform is an expression string like 'x => x 2' or 'x => x > 4 ? x : undefined'. Compile each via new Function('return (' + expr + ');')(). For each value, run all transforms in order. If a transform returns undefined, drop the value. Return the array of surviving values. solve([1, 2, 3, 4], ['x => x 2', 'x => x > 4 ? x : undefined']) → [6, 8].
Keep a log of recent timestamps. On each request, drop entries older than window. Allow if log size < limit; otherwise reject. solve(limit, window, times) returns array of allow/deny per request.
Implement min-heap with push and pop. solve(ops) runs operations: ['push', n] or ['pop'] (returns popped value or null). Returns array of pop results.
Parse a simplified multipart body. Body string is split by --<boundary> delimiters, with a final --<boundary>--. Each part has lines: Content-Disposition: form-data; name="<name>" then a blank line, then content. solve(boundary, body) returns { name: content }. Trim final \r\n if present.
LRU + per-entry TTL. solve(capacity, ttl, ops) runs operations: ['set', now, key, value] ['get', now, key] returns value or null. Expired entries return null and are evicted. Returns array of get results (in order).
Capacity C, leak rate L units per ms. solve(C, L, requests) where each request is [time, weight]. Before processing, leak based on elapsed time. If current + weight ≤ C, accept and add weight; else reject. Returns array of true/false.