Skip to content

Instantly share code, notes, and snippets.

@rajatk16
Created February 28, 2026 04:07
Show Gist options
  • Select an option

  • Save rajatk16/01bfe517ba8f5efbb3391b070a2de15c to your computer and use it in GitHub Desktop.

Select an option

Save rajatk16/01bfe517ba8f5efbb3391b070a2de15c to your computer and use it in GitHub Desktop.
Senior Software Developer Interview Questions

🧠 SECTION 1 — Node.js (Deep Theory + Practical)


Q1. Explain Node.js Event Loop in Depth

Expected Senior Answer

Node.js uses a single-threaded event loop backed by:

  • V8 (JS execution)
  • libuv (async I/O)

Phases:

  1. Timers
  2. Pending callbacks
  3. Idle/prepare
  4. Poll
  5. Check
  6. Close callbacks

Microtasks:

  • process.nextTick() → runs immediately after current operation
  • Promise.then() → runs after nextTick queue

👉 Important: nextTick can starve I/O.

Follow-up

What happens if CPU-heavy logic runs?

➡️ Blocks event loop → all requests stall

Solution

Use:

  • Worker Threads
  • Queue-based async processing (SQS / Kafka)

Q2. Difference Between process.nextTick() and setImmediate()

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.


Q3. How would you scale Node beyond a single core?

Expected answer:

  • Cluster module
  • PM2
  • Containers (K8s)
  • Load Balancer

Better senior answer:

👉 Stateless services 👉 External session store (Redis) 👉 Horizontal scaling


Q4. Streams vs Buffers

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)

Q5. What causes memory leaks in Node?

Expected:

  • Closures
  • Unremoved listeners
  • Global caches
  • Timers not cleared

Senior insight:

Use:

  • heapdump
  • clinic.js
  • Chrome DevTools

🧩 SECTION 2 — TypeScript (Senior Level)


Q6. Difference: type vs interface

type interface
Union support No unions
Can extend Can extend
Mapped types Yes

Use interface for:

👉 Public contracts Use type for:

👉 Composition / unions


Q7. What are Conditional Types?

type IsString<T> = T extends string ? true : false;

Used in:

  • API response typing
  • Utility libraries

Q8. Explain Generics in Real Backend Use

Example:

interface ApiResponse<T> {
  data: T;
  error?: string;
}

Senior insight:

Used in:

  • Repository pattern
  • SDK design
  • Middleware abstraction

Q9. What is Type Narrowing?

if (typeof value === "string") {
  // narrowed
}

Senior usage:

  • Safe parsing
  • Auth middleware
  • Event processors

Q10. How to enforce immutability?

Readonly<T>
as const

☁️ SECTION 3 — AWS (Real-world)


Q11. Design scalable API infra on AWS

Expected answer:

  • API Gateway
  • Lambda / ECS
  • RDS / DynamoDB
  • SQS
  • CloudFront
  • IAM

Senior insight:

👉 Add circuit breaker via SQS buffer


Q12. Lambda vs ECS?

Lambda ECS
Event driven Long-running
Auto-scale Manual control
Cold starts No cold start

Q13. When to use DynamoDB vs RDS?

Use DynamoDB when:

  • High scale
  • Predictable access
  • Event-driven systems

Use RDS when:

  • Complex queries
  • Transactions needed

Q14. Explain SQS Visibility Timeout

If consumer fails before timeout:

➡️ Message becomes visible again

Prevents message loss


Q15. How do you implement rate limiting?

Options:

  • API Gateway throttling
  • Redis token bucket
  • WAF

🏗 SECTION 4 — System Design


Q16. Design an API Rate Limiter

Approach:

  • Redis
  • Token bucket
  • TTL keys

Key = userId:timestamp


Q17. Design file upload system

Use:

  • S3 pre-signed URLs
  • Lambda virus scan
  • SQS processing

Q18. Design job queue

Use:

  • SQS
  • Worker ECS
  • DLQ

🧮 SECTION 5 — DS & Algorithms (Practical Coding)


Problem 1 — LRU Cache

Question

Design an in-memory LRU cache.

Solution (O(1))

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);
    }
  }
}

Problem 2 — Debounce Function

function debounce(fn: Function, delay: number) {
  let timer: NodeJS.Timeout;
  return (...args: any[]) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

Problem 3 — Detect Cycle in Graph

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;
}

Problem 4 — Top K Frequent Elements

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]);
}

Problem 5 — Sliding Window (Max Sum)

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;
}

🧠 SECTION 6 — Senior Behavioural Signals

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


🚀 BONUS — Real Senior Scenario

Question:

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


👨‍💻 LIVE CODING ROUND STRUCTURE

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

🟢 Problem 1 — Log Frequency Counter

Problem

You are given a list of API logs:

[
  "/users",
  "/orders",
  "/users",
  "/products",
  "/orders",
  "/users"
]

Return the most frequently called endpoint.


Expected Solution

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;
}

Follow-up

Return Top K endpoints


🟢 Problem 2 — First Non-Repeating Character

Useful for:

👉 Streaming systems 👉 Event uniqueness


Problem

Return first non-repeating character in string.

input: "aabbcddeff"
output: "c"

Solution

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;
}

🟢 Problem 3 — Two Sum (Real Backend Version)

Instead of indices, return:

👉 IDs of transactions that match target


Problem

transactions = [
  { id: "t1", amount: 100 },
  { id: "t2", amount: 300 },
  { id: "t3", amount: 200 },
  { id: "t4", amount: 400 }
]

target = 500

Return IDs that sum to target.


Solution

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 [];
}

🟢 Problem 4 — Sliding Window Rate Limit

Problem

Given request timestamps:

[1,2,3,7,8,9,10]

Max 3 requests allowed in 5 seconds.

Return if valid.


Solution

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;
}

🟢 Problem 5 — Group Anagrams

Real use:

👉 Duplicate payload detection 👉 Fraud clustering


Problem

["eat","tea","tan","ate","nat","bat"]

Solution

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()];
}

🟢 Problem 6 — Find Missing Number

Problem

Array contains numbers from 1..n with one missing.

[1,2,4,5]
=> 3

Solution

function 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;
}

🟢 Problem 7 — Flatten Nested Object

Backend-friendly problem.


Input

{
  user: {
    name: "Rajat",
    address: {
      city: "Mumbai"
    }
  }
}

Output

{
  "user.name": "Rajat",
  "user.address.city": "Mumbai"
}

Solution

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;
}

🟢 Problem 8 — Async Parallel Limit

Problem

Run promises with max concurrency.


Solution

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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment