File: notification-system/notification_system.py

Date: 2026-06-05

Time: 13:55

Purpose

This file implements a notification delivery system — the kind you'd design in a system design interview for services like Facebook notifications, Uber alerts, or any platform that sends push/SMS/email to users. It owns the full lifecycle: accepting notification requests, queuing them by priority, enforcing rate limits and user preferences, rendering templates, delivering through channel-specific providers, and retrying on failure.

It's a single-process simulation — no actual network calls, no real message brokers — but it faithfully models the architectural concerns: priority queuing, per-user per-channel rate limiting, exponential backoff with jitter, quiet hours, opt-out enforcement, and delivery status tracking.

Key Components

Enums

Data Classes

Delivery Channels

TemplateRegistry

Stores templates keyed by (name, channel) pairs — the same notification type can have different templates per channel (e.g., a short push vs. a rich email). Rendering uses regex substitution on {{var}} placeholders. Raises KeyError for missing templates or missing context variables — fail-fast, no silent defaults.

RateLimiter

Sliding-window rate limiter keyed by (userid, channel). The limits dict maps each channel to (maxcount, windowsize_seconds). Default limits: 3 pushes/minute, 1 SMS/minute, 5 emails/hour.

NotificationService

The orchestrator. Key methods:

Patterns

Priority queue with tie-breaking — The heap stores (priority, timestamp, seq, notification) tuples. priority gives CRITICAL-first ordering, timestamp gives FIFO within the same priority, and seq (a monotonic counter) breaks ties when timestamps collide. This three-level key is the standard pattern for stable priority queues with heapq.

Sliding window rate limiting — Uses a deque per (user, channel) pair, evicting entries older than the window on each check(). This is the classic approach from the rate limiter SDI chapter — O(1) amortized eviction, O(1) check.

Exponential backoff with jitter — On delivery failure, retry delay doubles each attempt (2^(n-1)) multiplied by a random factor in [0.5, 1.5]. This prevents thundering herd on provider recovery.

Two-phase rate limit checking — Rate limits are checked both at send() time (fast rejection) and again at process_queue() time (because time may have passed and new sends may have consumed budget between enqueue and delivery).

Status machine with audit trail — Every status transition goes through updatestatus(), which both sets the current status and appends to status_history. This gives a complete delivery timeline per notification.

Template-per-channel — Templates are keyed by (name, channel), so the same logical notification type (e.g., "order_confirmation") can render differently for push vs. email. This models how real notification systems adapt content to channel constraints.

Dependencies

Imports: All stdlib — heapq for priority queue, collections.deque for sliding window, re for template rendering, random for failure simulation and jitter, datetime for quiet-hours calculation, uuid (imported but unused — IDs are passed in externally).

Imported by: testnotificationsystem.py — the test suite is the only consumer, as expected for a standalone SDI implementation.

Flow

1. Setup: Create DeliveryChannel instances, wire them into NotificationService, register templates, set user preferences.

2. Enqueue: Call send() — notification gets opt-out check, rate limit check, then pushed to heap with QUEUED status.

3. Process: Call processqueue(currenttime) — pops from heap in priority order, checks scheduling/quiet-hours/rate-limits, renders content (template or raw), calls the channel's send(), handles success (record rate limit, update status) or failure (calculate backoff, re-enqueue or mark FAILED).

4. Query: Call getstatus(), getuserhistory(), or getstats() to inspect results.

The caller controls time — current_time is always passed explicitly, never read from the system clock. This makes the system fully deterministic and testable (modulo random for failure simulation).

Invariants

Error Handling

Topics to Explore

Beliefs