Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
- 12Relational Databases and Data Modeling for System Design
- 13Database Indexes and Query Performance: How to Make Reads Fast
- 14Transactions, Isolation Levels and Concurrency Control: Preventing Race Conditions
- 15Database Replication: Primary-Replica, Read Replicas, Lag and Failover
- 16Database Sharding and Partitioning: How It Works and When You Need It
- 17SQL vs NoSQL: Data Models and How to Choose the Right Database
6 · Distributed Systems
- 18Why Distributed Systems Are Hard: Partial Failure, Timeouts and Clocks
- 19CAP Theorem and Consistency Models Explained Without the Myths
- 20Idempotency, Retries and Message Delivery Guarantees
- 21Distributed Transactions: Two-Phase Commit, the Outbox Pattern and Sagas
- 22Distributed Locks, Leases, Leader Election and Consensus
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
- 33Design a Notification System: Email, SMS and Push at Scale
- 34Design a Chat System Like a Messaging App
- 35Design a News Feed: Fan-Out, Ranking and the Celebrity Problem
- 36Design Search Autocomplete (Typeahead)
- 37Design a Distributed Job Scheduler
- 38Design a Ticket Booking System Without Overselling
- 39Design a Payment System: Idempotency, Ledgers and Reconciliation
11 · Large-Scale Case Studies
- 40Design a Video Streaming Platform: Upload, Transcoding and Adaptive Streaming
- 41Design a Ride-Hailing System: Location, Matching and Real-Time Updates
- 42Design a Food Delivery Platform: Orders, Restaurants and Delivery Partners
- 43Design an E-Commerce System: Catalog, Cart, Checkout, Inventory and Flash Sales
- 44Design a Web Search Engine: Crawling, Inverted Indexes and Ranking
12 · Interview Readiness
Lesson 37 · Real-World Case Studies
Design a Distributed Job Scheduler
Design a service that runs millions of one-time and recurring jobs at the right time, at least once, across many machines: time-bucketed storage, dispatchers that own partitions through leases, atomic claims that prevent double dispatch, queues and workers with heartbeats, retries and dead letters, misfire policies after outages, top-of-the-hour spikes, time zones, and safe HTTP callbacks.
For
SDE-2 candidates preparing for scheduler or cron-at-scale questions, and engineers who need delayed and recurring work to run reliably.
Level
Before you start
Read Message Queues (article 11), Idempotency and Retries (article 20), Distributed Transactions (article 21) and Distributed Locks and Leader Election (article 22).
On this page
Key takeaways
- Separate deciding when a job is due (dispatch) from running it (execution); dispatchers enqueue due runs, and worker fleets execute them from queues.
- Store jobs indexed by due time and partition that index, so each dispatcher scans only its own partitions and only the current time window.
- Claim each run with an atomic conditional update keyed by the version or the scheduled time, so two dispatchers can never dispatch the same run twice.
- Execution is at least once, so every job handler must be idempotent, keyed by job id plus scheduled time.
- Plan for spikes at round times, misfires after outages, time zones and daylight-saving changes, and the security risks of calling user-supplied URLs.
Earlier designs kept needing "do this later": send a marketing push at 08:00 the user's time (article 33), release a ticket hold after 10 minutes (article 38), rebuild the autocomplete index every night (article 36), send a weekly digest (article 22). Each team could run its own cron jobs, and article 22 showed how that goes wrong: the job runs on every server, or on none when the one server dies.
A distributed job scheduler turns "run this at time T" and "run this every hour" into a reliable platform service: jobs run on time, at least once, never accidentally many times, and survive machine failures. It is a classic SDE-2 design because the hard parts are time, coordination and exactly the failure cases covered in Module 6.
What you will learn
#- How to scope a scheduler and estimate dispatch rates, including spikes at round times.
- Why scheduling and execution should be separate.
- Time-bucketed job storage and partitioned dispatchers with leases.
- Atomic claims that prevent double dispatch, with a runnable simulation.
- Workers, heartbeats, retries and dead letters.
- Recurring jobs, misfire policies, time zones and daylight saving.
- Failure handling, security for HTTP callbacks, and observability.
1. The problem
#Build a multi-tenant scheduling service. Internal services submit jobs to run once at a given time, after a delay, or on a recurring schedule (cron-like). When a job is due, the scheduler triggers it by putting a message on the tenant's queue or calling the tenant's HTTP endpoint. Jobs can be cancelled or rescheduled.
2. Clarify the requirements
#Question | Assumed answer | Impact |
|---|---|---|
Kinds of schedules? | One-time (at time T or after a delay) and recurring (cron expressions with a time zone) | Next-run computation |
Timing precision? | Within a few seconds of the scheduled time | Polling interval and preloading |
Execution guarantee? | At least once; job handlers are required to be idempotent | Simpler, robust design |
Who executes? | The scheduler only triggers; tenants' own workers execute (via queue), or the scheduler calls an HTTP endpoint | Bounded scheduler responsibility |
Payload? | Up to 64 KB of JSON | Storage |
Long-running jobs? | The tenant's worker runs them; the scheduler tracks acknowledgement for HTTP targets | Timeouts |
Retries? | Configurable per job: attempts and backoff | Retry state |
Missed runs after an outage? | Configurable misfire policy | Catch-up logic |
Functional requirements: create, cancel and reschedule one-time and recurring jobs; trigger each due run on time; retry failed triggers; record run history.
Non-functional requirements
Quality | Target |
|---|---|
Timeliness | p99 dispatch delay under 5 seconds |
Reliability | No scheduled run is lost |
Duplicates | A run is not dispatched twice under normal operation; handlers tolerate rare duplicates |
Availability | 99.95%; machine failures do not stop scheduling |
Scale | Below |
3. Assumptions and scale estimation
#One-time jobs: 100 million scheduled per day -> about 1,160 dispatches/s on averageRecurring jobs: 2 million definitions; on average each runs 24 times a day -> 48 million runs/day, about 560/s on averageTotal average: about 1,700 dispatches/sSpikes: people schedule at round times. Assume 10% of a day's runs fall on the first second of each hour, spread over 24 hours: (148 million x 10%) / 24 = about 620,000 runs due in the same second at the top of each hourStorage: 100 million one-time jobs/day x 1 KB = 100 GB/day; with 7 days of history, about 1 TB, plus recurring definitions (small)Conclusions: the average rate is modest, but spikes at round times are enormous: hundreds of thousands of runs due in the same second. Storage needs time-based partitioning. Precision of seconds means we cannot scan the whole table every second.
4. API design
#POST /v1/jobsIdempotency-Key: digest-user-42-2026-10-12{ "schedule": {"run_at": "2026-10-12T08:00:00+05:30"}, (or {"cron": "0 8 * * MON", "timezone": "Asia/Kolkata"}) "target": {"type": "queue", "name": "digest-jobs"}, (or {"type": "http", "url": "https://..."}) "payload": {"user_id": 42, "week": "2026-W41"}, "retry": {"max_attempts": 5, "backoff": "exponential"}, "misfire_policy": "run_once" (or "skip", "run_all")}201 Created { "job_id": "job_5f2c", "next_run_at": "2026-10-12T02:30:00Z" }DELETE /v1/jobs/{id} -> 204 (cancel future runs)PATCH /v1/jobs/{id} {"schedule": ...} -> 200 (reschedule)GET /v1/jobs/{id}/runs?cursor=... -> run historyTimes are stored in UTC; the time zone is kept for recurring schedules so the next run is computed correctly.
5. Data model
#jobs (job_id PK, tenant_id, schedule (run_at or cron + timezone), target, payload, retry_policy, misfire_policy, status (active, cancelled), next_run_at, version)due_index (bucket_minute, partition, next_run_at, job_id) -> one row per upcoming run; partitioned by (bucket_minute, partition) -> "all runs due in this minute for partitions I own" is a small range readruns (job_id, scheduled_at, attempt, status (dispatched, acked, failed), dispatched_at, finished_at, error) PRIMARY KEY (job_id, scheduled_at) -- one logical run per job per scheduled time- The due index is the heart of the design: it turns "find due jobs" into "read this minute's bucket", instead of scanning millions of rows. A time-ordered structure such as a sorted set keyed by run time works the same way at smaller scale (Redis sorted sets support range queries by score (Redis sorted sets)).
partitionis a hash ofjob_idinto, say, 1,024 partitions, so dispatch work can be split across machines.runswith primary key(job_id, scheduled_at)gives every run a stable identity, which is the idempotency key handlers use.
6. Baseline architecture
#One scheduler process, every second: SELECT * FROM jobs WHERE next_run_at <= now() AND status = 'active' for each: trigger it, then update next_run_at (or mark done)It works on one machine with a small table. Its problems are the subject of the next section.
7. What breaks first?
#- Single point of failure: if the one scheduler dies, nothing runs.
- Double dispatch: run two schedulers for availability, and both see the same due job.
- Scanning cost: querying a table of hundreds of millions of rows every second.
- Top-of-the-hour spikes: hundreds of thousands of runs due at once.
- Slow or failing targets: if triggering means calling an HTTP endpoint inline, one slow tenant delays everyone.
- Crashes between steps: a run triggered but its record not updated, or the reverse.
8. Scaling the design
#Separate dispatch from execution
#Tenants --API--> Job service --> jobs + due_index (partitioned DB)Dispatchers (several; each owns a set of partitions via leases) every second: read due_index for (current minute, my partitions) where next_run_at <= now for each run: claim it atomically -> write dispatch to the outbox -> nextOutbox relay --> target queues (tenant queues) or the HTTP-callback queueTenant workers (competing consumers) execute jobs, idempotently, keyed by (job_id, scheduled_at)HTTP callers (scheduler-owned workers) call tenant endpoints with timeouts and retriesDispatchers do only cheap work: read, claim, enqueue. Running jobs is someone else's problem, which keeps dispatch fast and isolates tenants. Microsoft's Scheduler Agent Supervisor pattern describes a similar division between scheduling steps, agents that perform them and a supervisor that monitors and recovers failed steps (Scheduler Agent Supervisor pattern).
Partitioned dispatchers with leases
#The 1,024 partitions are divided among dispatchers. Each dispatcher holds a lease for its partitions in a consistent coordination store (for example etcd, which provides leases built on Raft (Why etcd)), renewed every few seconds (article 22). When a dispatcher dies, its leases expire and the remaining dispatchers take over its partitions. Adding dispatchers rebalances partitions.
Leases alone are not enough: a paused dispatcher may wake up and keep dispatching after its lease has moved (article 22's process-pause problem, which Martin Kleppmann's analysis of distributed locking illustrates with long garbage-collection pauses (How to do distributed locking)). So the claim itself must be safe, as below. With safe claims, two dispatchers briefly touching the same partition is harmless.
Atomic claims
#A run is claimed with a conditional update against what the dispatcher read:
UPDATE jobsSET version = version + 1, next_run_at = :next_run_or_null -- for recurring jobs, the next occurrence, in the same updateWHERE job_id = :id AND version = :version_read AND status = 'active';-- 1 row: this dispatcher owns this run. 0 rows: someone else claimed it, or it was cancelled or changed.In the same transaction the dispatcher inserts the runs row (job_id, scheduled_at) and an outbox row for the queue message (article 21), so a claimed run is always enqueued exactly once by the relay, and an unclaimed one never is.
A subtle trap is worth knowing. If you claim by status alone ("set PENDING to DISPATCHED") and a recurring job's reschedule sets its status back to PENDING, a second dispatcher holding a stale read can claim the same run again. Claiming against the version read (or against the specific scheduled_at) fixes it. This Java program simulates two dispatchers that scan the same due jobs before either claims, with version-based claims; a recurring job reschedules itself in the same step.
// File: SchedulerDemo.javaimport java.util.ArrayList;import java.util.List;import java.util.TreeMap;public class SchedulerDemo { static class Job { final String id; final long everySeconds; // 0 = one-time job long nextRunAt; // seconds since the start of the simulation long version = 0; // incremented on every successful claim Job(String id, long nextRunAt, long everySeconds) { this.id = id; this.nextRunAt = nextRunAt; this.everySeconds = everySeconds; } } // What a dispatcher read when it scanned the due jobs. record Seen(Job job, long version, long runAt) {} // Due index: run time -> jobs due then (a stand-in for a time-bucketed table or a sorted set). static final TreeMap<Long, List<Job>> due = new TreeMap<>(); static final List<String> queue = new ArrayList<>(); static void schedule(Job job) { due.computeIfAbsent(job.nextRunAt, t -> new ArrayList<>()).add(job); } // Mimics: UPDATE jobs SET version = version + 1, next_run_at = ? WHERE id = ? AND version = ? static boolean claim(Seen seen) { Job job = seen.job(); if (job.version != seen.version()) { return false; // someone else claimed this run first } job.version++; queue.add(job.id + "@" + seen.runAt()); // enqueue the run for the workers if (job.everySeconds > 0) { // recurring: the next run is scheduled job.nextRunAt = seen.runAt() + job.everySeconds; // in the same atomic update schedule(job); } return true; } static void poll(String dispatcher, long now, List<Seen> scanned) { for (Seen seen : scanned) { String result = claim(seen) ? "dispatched" : "lost the race for"; System.out.println("t=" + now + " " + dispatcher + " " + result + " " + seen.job().id + " (run at " + seen.runAt() + ")"); } } public static void main(String[] args) { schedule(new Job("send-reminder-77", 10, 0)); schedule(new Job("hourly-report", 10, 60)); for (long now = 0; now <= 75; now += 5) { List<Job> dueNow = new ArrayList<>(); while (!due.isEmpty() && due.firstKey() <= now) { dueNow.addAll(due.pollFirstEntry().getValue()); } // Both dispatchers scan the same due jobs before either claims anything. List<Seen> scanA = new ArrayList<>(); List<Seen> scanB = new ArrayList<>(); for (Job job : dueNow) { scanA.add(new Seen(job, job.version, job.nextRunAt)); scanB.add(new Seen(job, job.version, job.nextRunAt)); } poll("dispatcher-A", now, scanA); poll("dispatcher-B", now, scanB); } System.out.println("queue: " + queue); }}t=10 dispatcher-A dispatched send-reminder-77 (run at 10)t=10 dispatcher-A dispatched hourly-report (run at 10)t=10 dispatcher-B lost the race for send-reminder-77 (run at 10)t=10 dispatcher-B lost the race for hourly-report (run at 10)t=70 dispatcher-A dispatched hourly-report (run at 70)t=70 dispatcher-B lost the race for hourly-report (run at 70)queue: [send-reminder-77@10, hourly-report@10, hourly-report@70]Each run appears in the queue exactly once, even though both dispatchers saw every due job.
Precise timing without hammering the database
#- Each dispatcher preloads the next minute's due runs for its partitions into memory (a priority queue or timing wheel keyed by run time), then fires each one when its time arrives and claims it then. The database is read once a minute per partition, not once a second.
- New jobs scheduled for "within the next minute" are also pushed to the owning dispatcher (or picked up by a short poll of the current bucket), so they are not missed.
- Use the database's clock (or one time source) for "now" in claims, not each dispatcher's clock (article 18).
Spikes at round times
#620,000 runs due in one second is a lot to claim, enqueue and execute at once, and the tenants' downstream systems may not cope either.
- Spread dispatch over a window: allow jobs to declare a tolerance ("between 00:00 and 00:05"), and use deterministic jitter (based on job id) within it. Many "daily at midnight" jobs do not care about the exact second.
- Batch claims and enqueues (claim hundreds of runs in one statement per partition).
- Rate-limit per tenant at the queue or HTTP-caller stage, so one tenant's 300,000 midnight jobs do not starve others.
- Scale dispatchers before known peaks (the top of every hour is predictable).
Execution: workers, heartbeats, retries
#- Queue targets: the run message carries
job_id,scheduled_atand payload. Tenant workers process it as competing consumers (Competing Consumers pattern), extend the message's visibility while working on long jobs (SQS describes extending the visibility timeout for exactly this (visibility timeout)), and acknowledge on success. - HTTP targets: scheduler-owned callers send a signed request with an idempotency header (
job_id:scheduled_at), a timeout, and retries with backoff and jitter per the job's policy; after the last attempt, the run goes to a dead-letter store and the tenant is alerted. - Idempotent handlers: at-least-once is the contract. A handler records
(job_id, scheduled_at)as processed (a unique key) in the same transaction as its effect (article 20). The digest example from article 22 does exactly this.
Recurring jobs, misfires and time zones
#- Next run is computed when a run is claimed, from the cron expression in the job's time zone, then converted to UTC for the due index.
- Daylight saving: in zones with daylight saving, some local times happen twice (clocks go back) and some never happen (clocks go forward). Define the rule (for example: a skipped time runs at the next valid instant; a repeated time runs once, at its first occurrence) and test it. India does not use daylight saving, but a global scheduler must handle zones that do.
- Misfires: if dispatch was down for 2 hours, a job that runs every 5 minutes missed 24 runs. Policy per job:
run_once(run one catch-up run now),run_all(run each missed run, carefully paced), orskip(wait for the next scheduled time). Catch-up after an outage must be rate-limited, or recovery itself becomes a spike.
Cancellation and rescheduling
#Cancellation sets status = 'cancelled' and increments the version; a dispatcher's claim then fails (the version changed). A run already dispatched may still execute; handlers that care should check the job's status (or a cancellation token) before doing irreversible work. Say this race out loud in an interview.
9. Failure scenarios and reliability
#Failure | Effect | Response |
|---|---|---|
A dispatcher crashes | Its partitions stop dispatching | Leases expire in seconds; others take over; due runs are still in the index |
A dispatcher pauses and resumes after losing its lease | It may try to claim runs | Version-based claims make its stale claims fail |
Crash after claiming, before enqueueing | Could lose a run | Claim and outbox row in one transaction; the relay enqueues later |
Queue outage | Dispatched runs cannot be enqueued | Outbox rows accumulate; relay catches up when the queue returns |
Worker crashes mid-job | Job unfinished | Message becomes visible again; retried; handler idempotent |
Tenant endpoint down | HTTP triggers fail | Retries with backoff; dead letter after the limit; tenant alerts; circuit breaker per tenant |
Database partition slow | Dispatch lag for its partitions | Partition-level metrics; replicas for reads of upcoming buckets |
Long outage | Many misfires | Misfire policy; rate-limited catch-up |
Clock skew between dispatchers | Early or late firing | One time source for "now" (database time) |
10. Consistency
#- Each run is claimed at most once (atomic conditional update), and every claimed run is enqueued (outbox). Execution is at least once (redelivery after crashes), and handlers make effects effectively once.
- Ordering between runs of the same recurring job is not guaranteed if a previous run is slow; jobs that must not overlap need a per-job concurrency limit (skip or delay a run while the previous one is still active).
- Cancellation is eventually effective for runs already dispatched.
- Run history is eventually consistent with execution (workers report completion).
11. Security
#- Tenant isolation: each tenant's jobs, queues and history are visible only to that tenant; per-tenant quotas on jobs and dispatch rate.
- HTTP targets are SSRF risks (article 28): a tenant could register
http://169.254.169.254/...or internal addresses. Allow only registered, verified destinations (or an allow-list of domains per tenant), resolve and block private ranges, and run callers in an isolated network segment. - Sign callbacks so tenants can verify requests came from the scheduler; include timestamps to prevent replay.
- Payloads may contain sensitive data: encrypt at rest, limit size, avoid logging them.
- Audit job creation, changes and cancellations.
12. Observability
#Signal | Why |
|---|---|
Dispatch delay (actual minus scheduled), p50 and p99, per partition | The timeliness SLO |
Due backlog (runs past due, not yet claimed) | Dispatchers falling behind |
Outbox backlog and relay lag | Enqueue delays |
Run outcomes, retries, dead letters per tenant | Tenant health |
Lease changes and partition ownership | Dispatcher stability |
Top-of-hour spike size and duration | Capacity planning |
Misfires and catch-up volume after incidents | Recovery behaviour |
13. Trade-offs
#Decision | Chosen | Alternative | Why |
|---|---|---|---|
Responsibility | Scheduler triggers; tenants execute | Scheduler runs all job code | Isolation and a small, reliable core |
Due lookup | Time-bucketed, partitioned index | Scan the jobs table | Constant work per minute regardless of total jobs |
Coordination | Leases per partition plus version-based claims | A single leader | Horizontal scale; safety even with stale lease holders |
Claim and enqueue | One transaction with an outbox | Separate steps | No lost or duplicated dispatches |
Execution guarantee | At least once, idempotent handlers | Attempted exactly once | Achievable and robust |
Precision | Preload the next minute, fire from memory | Poll the database every second | Accurate timing, low database load |
14. The final interview answer
#"The scheduler accepts one-time and recurring jobs and triggers each run within a few seconds of its time, at least once. That's about 1,700 runs a second on average, but up to hundreds of thousands due in the same second at the top of each hour.
Jobs live in a partitioned store with a due index bucketed by minute and partitioned by a hash of the job id. Dispatchers own partitions through leases in etcd; each preloads the next minute's due runs and fires them from memory. A run is claimed with a conditional update on the job's version, which also computes the next occurrence for recurring jobs in the job's time zone, and in the same transaction writes the run record and an outbox message, so a run is never lost and never dispatched twice, even if a paused dispatcher wakes up with a stale lease.
The relay puts runs on tenant queues or an HTTP-callback queue. Tenants' workers execute idempotently, keyed by job id and scheduled time; HTTP callers sign requests, retry with backoff and dead-letter failures, and only call verified destinations to avoid SSRF. Round-time spikes are smoothed with allowed windows and per-tenant rate limits, and outages are recovered with per-job misfire policies and paced catch-up."
Exercises
#Exercise 1
The midnight spike
#20 million daily jobs are scheduled "at midnight" in one time zone. Each enqueue takes about 0.2 ms of dispatcher work (in batches), and you run 10 dispatchers. How long does dispatching take without jitter? If tenants' workers can process 50,000 jobs per second in total, how long until all are done, and what would you change?
Exercise 2: Choose a misfire policy
#Choose run_once, run_all or skip for each job after a 3-hour outage, and explain:
- "Expire unpaid ticket holds every minute."
- "Send the daily sales report at 09:00."
- "Charge each subscription on its renewal date."
- "Refresh the trending-searches delta every 5 minutes."
Exercise 3: No overlapping runs
#A recurring job runs every 5 minutes but sometimes takes 8 minutes. The tenant says runs must never overlap. Design how the scheduler and the worker ensure that.
Exercise 4: The callback attack
#A tenant registers an HTTP target http://10.0.4.12:8080/admin/reset. What could happen, and what checks should the scheduler apply when a target is registered and when it is called?
Show solutionHide solution
Dispatch work: 20,000,000 x 0.2 ms = 4,000 s of dispatcher timeAcross 10 dispatchers in parallel: about 400 s, roughly 7 minutesExecution: 20,000,000 / 50,000 per second = 400 s, also about 7 minutes (overlapping with dispatch)So the last "midnight" jobs run around 00:07, and both the dispatchers and the tenants' workers are saturated for those minutes while other tenants' jobs wait.
Changes: let tenants declare a tolerance window (for example, "between 00:00 and 01:00") and spread runs with deterministic jitter; rate-limit per tenant so one tenant's burst does not delay others; scale dispatchers ahead of midnight; batch claims more aggressively; and challenge whether 20 million individual jobs are needed at all (one job that enqueues work in pages, like the campaign job in article 33, is often better).
Exercise 2
Show solutionHide solution
run_once. One catch-up run expires everything that is now overdue; running 180 missed runs adds nothing.run_once(if the report is still useful later in the day) orskip(if a late report is confusing). Most teams want it sent once on recovery, marked late.run_all, carefully: each missed renewal is a distinct obligation (a different subscription or period). Paced catch-up, idempotent per subscription and period, so nothing is charged twice.skip. Stale trending data has no value; the next scheduled run refreshes it.
Exercise 3
Show solutionHide solution
- Job-level concurrency policy
max_concurrent_runs: 1. When a run is due, the dispatcher checks whether the previous run is still active (itsrunsrow isdispatchedand not finished, within a maximum duration). If so, apply the policy: skip this run or delay it until the previous one finishes. - The worker also protects itself: it acquires a per-job lease (for example a row lock or a lease row with a fencing token) for the duration of the run, renewed by heartbeat, and refuses to start if another run holds it. That covers the case where the scheduler's view is stale.
- Make the job resumable or incremental so a skipped run does not lose work: the next run processes everything since the last successful one.
- Investigate why it takes 8 minutes; perhaps it should run less often or be split into smaller jobs.
Exercise 4
Show solutionHide solution
10.0.4.12 is a private address. If the scheduler's callers can reach the internal network, the tenant can make the scheduler call internal services (an admin endpoint here), bypassing network controls: a server-side request forgery (article 28). Other targets could be cloud metadata endpoints that return credentials.
Checks:
- At registration: require HTTPS to a domain the tenant has verified ownership of (for example via a DNS record or a challenge file), or to an allow-list approved per tenant. Reject IP literals, private, loopback and link-local ranges, and non-standard ports unless approved.
- At call time: resolve the hostname and re-check the resolved address against blocked ranges (DNS can change after registration); disable or re-validate redirects; enforce timeouts and response-size limits.
- Network isolation: run HTTP callers in a segment with egress only to the internet (through a proxy), never to internal networks or metadata endpoints.
- Sign requests and include a timestamp, so receivers can verify authenticity.
Where to go next
#- Next: Design a Ticket Booking System Without Overselling, which uses this scheduler to expire seat holds.
- Related: article 22 for leases and fencing, article 20 for idempotent handlers.
Sources & further reading
- Technology
- Software Engineering
- #case study
- #distributed systems
- #roadmap
- #scalability
- #system design
- #system design interview
Connected lessons
Linked from 1 guide
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
Start Here: Your Roadmap from Zero to Job-Ready DSA
Guide · Technology
Heaps and Priority Queues
Guide · Career
A LinkedIn Profile That Gets Recruiter Messages

