Skip to the solution
MediumSystem Design13 min read

What are the trade-offs of microservices vs a monolith?

499 views

The problem

Question presented to candidate: "We are a growing company with one big application. Some engineers want to split it into microservices. What are the real trade-offs, and how would you decide?"

What a strong answer should cover:

  • Clear definitions: a monolith is one deployable unit (ideally a modular monolith with enforced boundaries); microservices are independently deployable services, each owning its data, talking over the network
  • What the network costs: latency per hop, tail latency amplification with fan-out, availability that multiplies across synchronous dependencies, partial failure, versioned APIs
  • Data: one ACID transaction in a monolith versus sagas with compensations and visible intermediate states across services; no cross-service joins
  • What microservices buy: independent deploys and scaling, fault and team isolation, technology choice per service; the price in operations (observability, CI/CD per service, service discovery, on-call)
  • A decision rule driven by team size and real pain points, migration with the strangler fig pattern, and real case studies in both directions (Segment, Prime Video)

Clarifying questions expected:

  • How many engineers and teams, and where is the pain today: deploy conflicts, scaling, reliability, or slow builds?
  • Do parts of the system have very different load, latency or compliance needs?

Code / implementation expected: Optional โ€” the example quantifies hop latency, fan-out tails, availability chains, rollback versus saga, scaling memory and team communication paths.

  • microservices

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read ยท 15 sections

Read the code

Monolith vs microservices in numbers: hop latency and fan-out tails, availability chains, transaction vs saga, scaling and team paths
// Monolith vs microservices in numbers: what a network hop costs, how fan-out amplifies the tail,
// how availability multiplies, a local transaction vs a saga, and scaling one hot component.

let seed = 7; // 32-bit LCG so every language draws the same numbers
const rand = () => { seed = (seed * 1664525 + 1013904223) % 4294967296; return seed / 4294967296; };
// One network call: 2 ms usually, 50 ms 1% of the time (GC pause, retry, noisy neighbour). Assumed.
const hop = () => (rand() < 0.01 ? 50 : 2);
const inProcess = 0.001; // an in-process function call, in ms (assumed)
const pct = (xs, p) => { const s = [...xs].sort((a, b) => a - b); return s[Math.ceil((p / 100) * s.length) - 1]; };

// ---- 1. latency of one checkout request that needs 5 components ----
const N = 100000;
const serial = [], fanout5 = [], fanout50 = [];
let slow5 = 0, slow50 = 0;
for (let i = 0; i < N; i++) {
  let s = 0; for (let k = 0; k < 5; k++) s += hop(); serial.push(s);
  let m = 0; for (let k = 0; k < 5; k++) m = Math.max(m, hop()); fanout5.push(m); if (m === 50) slow5++;
  let m2 = 0; for (let k = 0; k < 50; k++) m2 = Math.max(m2, hop()); fanout50.push(m2); if (m2 === 50) slow50++;
}
console.log("== 1. latency (ms), 100000 simulated requests ==");
console.log(`  monolith, 5 in-process calls:       p50=${(5 * inProcess).toFixed(3)}  p99=${(5 * inProcess).toFixed(3)}`);
console.log(`  5 services called one after another: p50=${pct(serial, 50)}  p99=${pct(serial, 99)}  p99.9=${pct(serial, 99.9)}`);
console.log(`  5 services called in parallel:       p50=${pct(fanout5, 50)}  p99=${pct(fanout5, 99)}  slow requests=${(slow5 / N * 100).toFixed(2)}% (formula ${((1 - 0.99 ** 5) * 100).toFixed(2)}%)`);
console.log(`  50 services called in parallel:      p50=${pct(fanout50, 50)}  p99=${pct(fanout50, 99)}  slow requests=${(slow50 / N * 100).toFixed(2)}% (formula ${((1 - 0.99 ** 50) * 100).toFixed(2)}%)`);

// ---- 2. availability: every synchronous dependency multiplies in ----
const a = 0.999;
console.log("== 2. availability, each service 99.9% ==");
for (const n of [1, 5, 10]) {
  const av = a ** n;
  console.log(`  ${String(n).padStart(2)} in the chain: ${(av * 100).toFixed(2)}%  downtime ${((1 - av) * 525600).toFixed(0)} min/year`);
}

// ---- 3. place an order: one local transaction vs a saga across services ----
function placeOrder(style, paymentFails) {
  const log = [];
  let stock = 10, orders = 0;
  if (style === "monolith") {
    const snapshot = { stock, orders }; log.push("BEGIN");
    stock -= 1; orders += 1; log.push("stock -1, order +1 (not yet visible)");
    if (paymentFails) { ({ stock, orders } = snapshot); log.push("payment: declined -> ROLLBACK"); } else log.push("COMMIT");
    log.push(`others ever saw stock=${paymentFails ? 10 : 9}`);
  } else {
    stock -= 1; log.push("inventory: reserved (committed)");
    orders += 1; log.push("orders: created PENDING (committed)");
    const seenByOthers = stock;
    if (paymentFails) {
      log.push("payment: declined");
      orders -= 1; log.push("orders: compensate -> CANCELLED");
      stock += 1; log.push("inventory: compensate -> released");
    } else log.push("payment: charged");
    log.push(`others could see stock=${seenByOthers} in between`);
  }
  log.push(`final stock=${stock} orders=${orders}`);
  return log;
}
console.log("== 3. payment declined ==");
for (const style of ["monolith", "saga"]) {
  console.log(`  ${style}:`);
  for (const line of placeOrder(style, true)) console.log("    " + line);
}

// ---- 4. scaling one hot component (image resizing needs 10 replicas, the rest needs 2) ----
const monolithGB = 4, imageSvcGB = 1, restGB = 3;
console.log("== 4. memory to run 10 copies of the hot path ==");
console.log(`  monolith: 10 replicas x ${monolithGB} GB = ${10 * monolithGB} GB`);
console.log(`  services: 10 x ${imageSvcGB} GB image service + 2 x ${restGB} GB rest = ${10 * imageSvcGB + 2 * restGB} GB`);

// ---- 5. people: communication paths n(n-1)/2 ----
const paths = (n) => (n * (n - 1)) / 2;
console.log("== 5. 48 engineers ==");
console.log(`  one shared codebase, everyone coordinates: ${paths(48)} paths`);
console.log(`  6 teams of 8 with owned services: ${6 * paths(8)} inside teams + ${paths(6)} team-to-team interfaces = ${6 * paths(8) + paths(6)}`);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 36 of 106 in System design

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What is a CDN and how does it work?

Continue