Karat Technical Interview Mock Exam: 15 JavaScript Questions with Detailed Solutions

Prepare for a Karat technical interview with a realistic 60-minute mock exam, five discussion questions, ten original JavaScript coding problems, detailed solutions, edge cases, follow-ups, and complexity analysis.
Karat Technical Interview Mock Exam: 15 JavaScript Questions with Detailed Solutions
A Karat technical interview tests more than code. You must understand requirements, explain decisions, handle edge cases, test your work, and discuss complexity while an interviewer observes your process.
This mock exam contains five discussion questions and ten original JavaScript coding problems. They are practice exercises, not leaked, recalled, or official Karat questions. Content varies by company, role, and seniority.
The official Karat Candidate Experience describes a typical 60-minute interview with a brief introduction, about 10 minutes of discussion questions, and about 40 minutes of programming. Always follow the instructions sent for your interview.
A 60-Minute Mock Interview
| Time | Activity |
|---|---|
| 0–5 minutes | Give a concise introduction |
| 5–15 minutes | Answer two discussion questions |
| 15–35 minutes | Solve coding problem one |
| 35–55 minutes | Solve coding problem two and a follow-up |
| 55–60 minutes | Test, fix, and summarize complexity |
For every coding question:
- Restate the input, output, and success rule.
- Ask about empty input, duplicates, ordering, and constraints.
- Walk through one example.
- Explain the data structure.
- Implement a correct solution.
- Test and state time and space complexity.
Part One: Five Discussion Questions
1. When Should You Use Map Instead of an Object?
Use an object for a record with known property names. Use Map when keys are dynamic, may be non-strings, or require frequent insertion and lookup.
“For grouping requests by an arbitrary user ID, I would use
Map. For a configuration with fixed fields such ashostandport, I would use an object.”
Map also provides size, has, get, set, and insertion-order iteration.
2. Explain the JavaScript Event Loop
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
Output:
A
D
C
B
Synchronous code runs first. Promise callbacks enter the microtask queue, while the timer callback enters the task queue. Microtasks run before the next task after the current stack is empty.
3. Debouncing vs. Throttling
- Debounce: run after calls stop for a period. Example: search after the user pauses typing.
- Throttle: run at most once per interval. Example: update scroll position every 200 milliseconds.
Explain the product effect, not only the definition.
4. How Do You Prevent Async Race Conditions?
Completion order is not guaranteed. Depending on the system, use AbortController, request-version checks, idempotency keys, transactions, optimistic locking, or queues.
let latestRequest = 0;
async function search(query) {
const id = ++latestRequest;
const data = await fetchResults(query);
if (id === latestRequest) renderResults(data);
}
This ignores a stale response when a newer search has already started.
5. How Would You Investigate a Slow API?
Define “slow” using p50, p95, and p99 latency. Determine whether the issue affects one endpoint, customer, or time period. Trace application, database, cache, and external-service time. Check recent changes and resource saturation. Optimize the measured bottleneck, verify the result, and add monitoring.
Part Two: Ten JavaScript Coding Problems
Problem 1: Keep the Latest Event
Return one record per ID. Keep the greatest timestamp while preserving the order in which each ID first appeared.
function latestEvents(events) {
const latest = new Map();
for (const event of events) {
const current = latest.get(event.id);
if (!current || event.timestamp > current.timestamp) {
latest.set(event.id, event);
}
}
return [...latest.values()];
}
latestEvents([
{ id: "A", timestamp: 10, value: "old" },
{ id: "B", timestamp: 20, value: "open" },
{ id: "A", timestamp: 30, value: "new" }
]);
// A at 30, then B at 20
Replacing a map value does not change key insertion order.
- Time: O(n)
- Space: O(k) for
kdistinct IDs - Follow-up: sort the result if latest-timestamp order is required.
Problem 2: First Unique Request ID
Return the first ID appearing exactly once, or null.
function firstUnique(ids) {
const counts = new Map();
for (const id of ids) {
counts.set(id, (counts.get(id) ?? 0) + 1);
}
for (const id of ids) {
if (counts.get(id) === 1) return id;
}
return null;
}
firstUnique(["r1", "r2", "r1", "r3", "r2"]); // "r3"
The first pass counts; the second preserves order.
- Time: O(n)
- Space: O(k)
- Test: empty input, one ID, and no unique IDs.
Problem 3: Merge Maintenance Windows
Merge intervals that overlap or touch without modifying the input.
function mergeWindows(windows) {
if (windows.length === 0) return [];
const sorted = windows
.map(([start, end]) => [start, end])
.sort((a, b) => a[0] - b[0]);
const result = [sorted[0]];
for (let i = 1; i < sorted.length; i++) {
const current = sorted[i];
const last = result[result.length - 1];
if (current[0] <= last[1]) {
last[1] = Math.max(last[1], current[1]);
} else {
result.push(current);
}
}
return result;
}
mergeWindows([[30, 60], [10, 20], [20, 25], [55, 80]]);
// [[10, 25], [30, 80]]
- Time: O(n log n)
- Space: O(n)
- Follow-up: already-sorted intervals reduce the time to O(n).
Problem 4: Apply Inventory Changes
Apply changes in order. Reject a change that would make inventory negative.
function updateInventory(initial, changes) {
const inventory = { ...initial };
const rejected = [];
for (const change of changes) {
const current = inventory[change.product] ?? 0;
const next = current + change.amount;
if (next < 0) {
rejected.push(change);
} else {
inventory[change.product] = next;
}
}
return { inventory, rejected };
}
updateInventory(
{ keyboard: 4, mouse: 2 },
[
{ product: "keyboard", amount: -2 },
{ product: "mouse", amount: -3 },
{ product: "monitor", amount: 5 }
]
);
- Time: O(n + p)
- Space: O(p + r)
- Follow-up: concurrent updates may require a transaction or optimistic lock.
Problem 5: Validate Nested Delimiters
Check (), [], and {} while ignoring other characters.
function isBalanced(source) {
const expected = { ")": "(", "]": "[", "}": "{" };
const opening = new Set(["(", "[", "{"]);
const stack = [];
for (const character of source) {
if (opening.has(character)) {
stack.push(character);
} else if (character in expected) {
if (stack.pop() !== expected[character]) return false;
}
}
return stack.length === 0;
}
isBalanced("{ value: [1, (2 + 3)] }"); // true
isBalanced("([)]"); // false
- Time: O(n)
- Space: O(n)
- Follow-up: a real parser must ignore delimiters inside strings and comments.
Problem 6: Per-User Rate Limiter
Allow at most three accepted requests per user in an inclusive 10-second window. Timestamps arrive in order.
function createLimiter(limit, seconds) {
const state = new Map();
return function allow(user, time) {
const data = state.get(user) ?? { times: [], head: 0 };
const minimum = time - seconds;
while (
data.head < data.times.length &&
data.times[data.head] < minimum
) {
data.head++;
}
if (data.times.length - data.head >= limit) {
state.set(user, data);
return false;
}
data.times.push(time);
state.set(user, data);
return true;
};
}
const allow = createLimiter(3, 10);
allow("A", 1); // true
allow("A", 4); // true
allow("A", 8); // true
allow("A", 9); // false
allow("A", 12); // true
Each accepted timestamp enters and leaves the active window once.
- Amortized time per call: O(1)
- Follow-up: discuss inactive-user cleanup and multiple servers.
Problem 7: Find a Valid Build Order
[project, dependency] means the dependency must be built first. Return null for a cycle.
function buildOrder(projects, dependencies) {
const next = new Map(projects.map(project => [project, []]));
const degree = new Map(projects.map(project => [project, 0]));
for (const [project, dependency] of dependencies) {
next.get(dependency).push(project);
degree.set(project, degree.get(project) + 1);
}
const queue = projects.filter(project => degree.get(project) === 0);
const result = [];
let head = 0;
while (head < queue.length) {
const completed = queue[head++];
result.push(completed);
for (const project of next.get(completed)) {
degree.set(project, degree.get(project) - 1);
if (degree.get(project) === 0) queue.push(project);
}
}
return result.length === projects.length ? result : null;
}
buildOrder(
["api", "web", "db", "auth"],
[["api", "db"], ["api", "auth"], ["web", "api"]]
);
This is Kahn’s topological sort.
- Time: O(V + E)
- Space: O(V + E)
Problem 8: Shortest Path in a Grid
0 is open and 1 is blocked. Move vertically or horizontally.
function shortestPath(grid, start, end) {
const rows = grid.length;
if (rows === 0) return -1;
const queue = [[...start, 0]];
const seen = new Set([start.join(",")]);
const moves = [[1, 0], [-1, 0], [0, 1], [0, -1]];
let head = 0;
while (head < queue.length) {
const [row, column, distance] = queue[head++];
if (row === end[0] && column === end[1]) return distance;
for (const [dr, dc] of moves) {
const nr = row + dr;
const nc = column + dc;
const key = `${nr},${nc}`;
if (
nr >= 0 && nr < rows &&
nc >= 0 && nc < grid[0].length &&
grid[nr][nc] === 0 &&
!seen.has(key)
) {
seen.add(key);
queue.push([nr, nc, distance + 1]);
}
}
}
return -1;
}
Breadth-first search gives the shortest path in an unweighted grid.
- Time and space: O(rows × columns)
- Follow-up: use Dijkstra’s algorithm when moves have different costs.
Problem 9: Top API Endpoints
Return the k most frequent endpoints. Break ties alphabetically.
function topEndpoints(endpoints, k) {
const counts = new Map();
for (const endpoint of endpoints) {
counts.set(endpoint, (counts.get(endpoint) ?? 0) + 1);
}
return [...counts.entries()]
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.slice(0, k)
.map(([endpoint]) => endpoint);
}
topEndpoints(
["/users", "/orders", "/users", "/health", "/orders"],
2
);
// ["/orders", "/users"] because the tie is alphabetical
For n requests and u unique endpoints:
- Time: O(n + u log u)
- Space: O(u)
- Follow-up: a min-heap improves selection when
kis small.
Problem 10: Compare Nested Configurations
Return paths whose values differ. Treat arrays as complete values.
function isObject(value) {
return value !== null &&
typeof value === "object" &&
!Array.isArray(value);
}
function differentPaths(before, after) {
const result = [];
function compare(left, right, path = "") {
const keys = new Set([
...Object.keys(left ?? {}),
...Object.keys(right ?? {})
]);
for (const key of keys) {
const nextPath = path ? `${path}.${key}` : key;
const leftHas = Object.hasOwn(left ?? {}, key);
const rightHas = Object.hasOwn(right ?? {}, key);
if (!leftHas || !rightHas) {
result.push(nextPath);
} else if (isObject(left[key]) && isObject(right[key])) {
compare(left[key], right[key], nextPath);
} else if (!Object.is(left[key], right[key])) {
result.push(nextPath);
}
}
}
compare(before, after);
return result.sort();
}
differentPaths(
{ server: { port: 3000 }, logging: true },
{ server: { port: 8080 }, logging: false, region: "us" }
);
// ["logging", "region", "server.port"]
- Time: O(n + d log d), including result sorting
- Space: O(depth + d)
- Follow-up: clarify whether arrays should be compared whole, by index, or by ID.
A Strong Interview Conversation
For the interval problem, you might say:
“Let me confirm that touching intervals should merge and that I should not modify the input. I’ll sort a copy by start time. Then every interval only needs to be compared with the last merged interval. Sorting takes O(n log n), and the merge pass is O(n). I’ll test empty input, nested intervals, touching intervals, and non-overlapping intervals.”
This is concise but shows understanding, planning, testing, and complexity awareness.
Common Mistakes
- Coding before confirming the requirement
- Remaining silent for several minutes
- Optimizing before producing a correct baseline
- Forgetting empty input, duplicates, and boundaries
- Using clever JavaScript that is difficult to explain
- Claiming correctness without walking through a test
- Stating time complexity but forgetting space complexity
Useful phrases:
- “The invariant I want to preserve is…”
- “This version is correct but not yet optimal.”
- “I’m checking this boundary because…”
- “I see a mistake in my condition; I’ll correct it.”
- “I would like to test one normal case and one failure case.”
Seven-Day Preparation Plan
- Communication: prepare your introduction and practise restating problems.
- Maps and sets: solve Problems 1, 2, and 4.
- Sorting: solve Problems 3 and 9.
- Stacks and windows: solve Problems 5 and 6.
- Graphs: solve Problems 7 and 8.
- Recursion: solve Problem 10 and answer all discussion questions aloud.
- Mock interview: choose two random problems and complete the 60-minute schedule.
Final Checklist
Before the interview, test your camera, microphone, connection, and browser. Re-read the specific rules about references or AI tools; do not assume.
For each question:
- Restate the requirement.
- Ask relevant clarifying questions.
- Explain the data structure.
- Write a correct baseline.
- Test normal and edge cases.
- State time and space complexity.
- Respond calmly to hints and follow-ups.
The strongest preparation is not memorizing supposed Karat questions. Practise the complete process: understand, clarify, plan, implement, test, analyse, and adapt.
Official References
Featured Articles

Kafka in Production: Partition Strategy, Consumer Lag, Reliability, and an Incident Playbook
A production Kafka cluster needs more than brokers. Learn to choose partition keys, plan retention and capacity, monitor lag, handle rebalances, and rehearse failure recovery.

Laravel and Kafka Without Lost Events: The Transactional Outbox, Idempotent Consumers, and PostgreSQL
A database commit and a Kafka publish cannot safely be treated as one ordinary transaction. This guide builds an outbox and consumer design that survives crashes and duplicate delivery.

Apache Kafka Explained: Topics, Partitions, Consumer Groups, and Your First Event Pipeline
Follow one order event from producer to consumers, then run a local Kafka topic and learn what partitions, offsets, keys, and consumer groups actually do.
Comments
0 commentsNo approved comments are visible yet. New community replies may wait for moderation.