Node.js uses a single-threaded event loop backed by:
- V8 (JS execution)
- libuv (async I/O)
Phases:
- Timers
- Pending callbacks
- Idle/prepare
- Poll
- Check
- Close callbacks
Microtasks:
process.nextTick()→ runs immediately after current operationPromise.then()→ runs after nextTick queue
👉 Important: nextTick can starve I/O.
What happens if CPU-heavy logic runs?
➡️ Blocks event loop → all requests stall
Use:
- Worker Threads
- Queue-based async processing (SQS / Kafka)
| Feature | nextTick | setImmediate |
|---|---|---|
| Queue | Microtask | Check phase |
| Runs before I/O? | Yes | No |
| Can block loop? | Yes | No |
Senior insight:
Overusing nextTick can cause event loop starvation.
Expected answer:
- Cluster module
- PM2
- Containers (K8s)
- Load Balancer
Better senior answer:
👉 Stateless services 👉 External session store (Redis) 👉 Horizontal scaling
| Streams | Buffers |
|---|---|
| Process chunks | Load full data |
| Memory efficient | Memory heavy |
| Used in files/network | Used in small payloads |
Example:
fs.createReadStream("big.log").pipe(res)Expected:
- Closures
- Unremoved listeners
- Global caches
- Timers not cleared
Senior insight:
Use:
- heapdump
- clinic.js
- Chrome DevTools
| type | interface |
|---|---|
| Union support | No unions |
| Can extend | Can extend |
| Mapped types | Yes |
Use interface for:
👉 Public contracts Use type for:
👉 Composition / unions
type IsString<T> = T extends string ? true : false;Used in:
- API response typing
- Utility libraries
Example:
interface ApiResponse<T> {
data: T;
error?: string;
}Senior insight:
Used in:
- Repository pattern
- SDK design
- Middleware abstraction
if (typeof value === "string") {
// narrowed
}Senior usage:
- Safe parsing
- Auth middleware
- Event processors
Readonly<T>
as constExpected answer:
- API Gateway
- Lambda / ECS
- RDS / DynamoDB
- SQS
- CloudFront
- IAM
Senior insight:
👉 Add circuit breaker via SQS buffer
| Lambda | ECS |
|---|---|
| Event driven | Long-running |
| Auto-scale | Manual control |
| Cold starts | No cold start |
Use DynamoDB when:
- High scale
- Predictable access
- Event-driven systems
Use RDS when:
- Complex queries
- Transactions needed
If consumer fails before timeout:
➡️ Message becomes visible again
Prevents message loss
Options:
- API Gateway throttling
- Redis token bucket
- WAF
Approach:
- Redis
- Token bucket
- TTL keys
Key = userId:timestamp
Use:
- S3 pre-signed URLs
- Lambda virus scan
- SQS processing
Use:
- SQS
- Worker ECS
- DLQ
Design an in-memory LRU cache.
Use:
- Map
- Doubly Linked List
class LRUCache {
private cache = new Map<number, number>();
constructor(private capacity: number) {}
get(key: number): number {
if (!this.cache.has(key)) return -1;
const val = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, val);
return val;
}
put(key: number, value: number): void {
if (this.cache.has(key)) this.cache.delete(key);
this.cache.set(key, value);
if (this.cache.size > this.capacity) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
}function debounce(fn: Function, delay: number) {
let timer: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}DFS approach:
function hasCycle(graph: Map<number, number[]>): boolean {
const visited = new Set<number>();
const stack = new Set<number>();
function dfs(node: number): boolean {
if (stack.has(node)) return true;
if (visited.has(node)) return false;
visited.add(node);
stack.add(node);
for (const nei of graph.get(node) || []) {
if (dfs(nei)) return true;
}
stack.delete(node);
return false;
}
for (const node of graph.keys()) {
if (dfs(node)) return true;
}
return false;
}function topK(nums: number[], k: number): number[] {
const map = new Map<number, number>();
nums.forEach(n => map.set(n, (map.get(n) || 0) + 1));
return [...map.entries()]
.sort((a, b) => b[1] - a[1])
.slice(0, k)
.map(x => x[0]);
}function maxSum(arr: number[], k: number): number {
let sum = 0;
for (let i = 0; i < k; i++) sum += arr[i];
let max = sum;
for (let i = k; i < arr.length; i++) {
sum += arr[i] - arr[i - k];
max = Math.max(max, sum);
}
return max;
}Ask:
- How do you handle production incidents?
- How do you design backward-compatible APIs?
- When do you introduce async processing?
Strong answers show:
👉 Tradeoffs 👉 Observability thinking 👉 Failure design
Your Node API is slowing down under load.
Expected Answer:
- Identify CPU vs I/O bottleneck
- Add caching
- Move heavy jobs to queue
- Horizontal scale
- Profile memory
Great — here’s a Live Coding Round designed for a Senior Node / TypeScript Engineer that:
✅ Tests problem solving (not LeetCode trickery) ✅ Reflects real backend thinking ✅ Uses DS + Algorithms ✅ Is slightly easier but still signal-rich
Duration: 60 mins
| Round | Topic | Time |
|---|---|---|
| Problem 1 | Data Processing | 15 min |
| Problem 2 | Sliding Window | 15 min |
| Problem 3 | Map / Hashing | 15 min |
| Problem 4 | Async Thinking | 15 min |
You are given a list of API logs:
[
"/users",
"/orders",
"/users",
"/products",
"/orders",
"/users"
]Return the most frequently called endpoint.
Use HashMap
function mostFrequentEndpoint(logs: string[]): string {
const freq = new Map<string, number>();
for (const log of logs) {
freq.set(log, (freq.get(log) || 0) + 1);
}
let maxEndpoint = "";
let maxCount = 0;
for (const [endpoint, count] of freq) {
if (count > maxCount) {
maxCount = count;
maxEndpoint = endpoint;
}
}
return maxEndpoint;
}Return Top K endpoints
Useful for:
👉 Streaming systems 👉 Event uniqueness
Return first non-repeating character in string.
input: "aabbcddeff"
output: "c"function firstUniqueChar(str: string): string | null {
const freq = new Map<string, number>();
for (const ch of str) {
freq.set(ch, (freq.get(ch) || 0) + 1);
}
for (const ch of str) {
if (freq.get(ch) === 1) return ch;
}
return null;
}Instead of indices, return:
👉 IDs of transactions that match target
transactions = [
{ id: "t1", amount: 100 },
{ id: "t2", amount: 300 },
{ id: "t3", amount: 200 },
{ id: "t4", amount: 400 }
]
target = 500Return IDs that sum to target.
function findTransactions(transactions: {id: string, amount: number}[], target: number) {
const map = new Map<number, string>();
for (const t of transactions) {
const needed = target - t.amount;
if (map.has(needed)) {
return [map.get(needed), t.id];
}
map.set(t.amount, t.id);
}
return [];
}Given request timestamps:
[1,2,3,7,8,9,10]Max 3 requests allowed in 5 seconds.
Return if valid.
function isRateLimitValid(times: number[], maxReq: number, window: number): boolean {
let left = 0;
for (let right = 0; right < times.length; right++) {
while (times[right] - times[left] >= window) {
left++;
}
if (right - left + 1 > maxReq) {
return false;
}
}
return true;
}Real use:
👉 Duplicate payload detection 👉 Fraud clustering
["eat","tea","tan","ate","nat","bat"]function groupAnagrams(words: string[]): string[][] {
const map = new Map<string, string[]>();
for (const word of words) {
const key = word.split("").sort().join("");
if (!map.has(key)) {
map.set(key, []);
}
map.get(key)!.push(word);
}
return [...map.values()];
}Array contains numbers from 1..n with one missing.
[1,2,4,5]
=> 3function findMissing(nums: number[]): number {
const n = nums.length + 1;
const expected = (n * (n + 1)) / 2;
const actual = nums.reduce((a, b) => a + b, 0);
return expected - actual;
}Backend-friendly problem.
{
user: {
name: "Rajat",
address: {
city: "Mumbai"
}
}
}{
"user.name": "Rajat",
"user.address.city": "Mumbai"
}function flatten(obj: any, prefix = "", res: any = {}) {
for (const key in obj) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null) {
flatten(obj[key], newKey, res);
} else {
res[newKey] = obj[key];
}
}
return res;
}Run promises with max concurrency.
async function runWithLimit(tasks: (() => Promise<any>)[], limit: number) {
const results: any[] = [];
let index = 0;
async function worker() {
while (index < tasks.length) {
const current = index++;
results[current] = await tasks[current]();
}
}
await Promise.all(Array(limit).fill(0).map(worker));
return results;
}