diff --git a/.claude/hooks/locks/51726d25-d24f-48e8-bb5d-a775dc434ee4.lock b/.claude/hooks/locks/51726d25-d24f-48e8-bb5d-a775dc434ee4.lock new file mode 100644 index 0000000..b9c8a17 --- /dev/null +++ b/.claude/hooks/locks/51726d25-d24f-48e8-bb5d-a775dc434ee4.lock @@ -0,0 +1 @@ +Tue Jan 6 23:38:01 IST 2026 diff --git a/.claude/hooks/session-end.log b/.claude/hooks/session-end.log new file mode 100644 index 0000000..5d8b1a2 --- /dev/null +++ b/.claude/hooks/session-end.log @@ -0,0 +1,7 @@ +[2026-01-06 23:38:01] Hook triggered with input: {"session_id":"51726d25-d24f-48e8-bb5d-a775dc434ee4","transcript_path":"/Users/rathi/.claude/projects/-Users-rathi-Documents-GitHub-infra-web/51726d25-d24f-48e8-bb5d-a775dc434ee4.jsonl","cwd":"/Users/rathi/Documents/GitHub/infra-web","hook_event_name":"SessionEnd","reason":"prompt_input_exit"} +[2026-01-06 23:38:01] Transcript: /Users/rathi/.claude/projects/-Users-rathi-Documents-GitHub-infra-web/51726d25-d24f-48e8-bb5d-a775dc434ee4.jsonl +[2026-01-06 23:38:01] Session ID: 51726d25-d24f-48e8-bb5d-a775dc434ee4 +[2026-01-06 23:38:01] Project dir: /Users/rathi/Documents/GitHub/infra-web +[2026-01-06 23:38:01] Lock file created +[2026-01-06 23:38:01] Transcript line count: 4 +[2026-01-06 23:38:01] Session too short, skipping retrospective diff --git a/README.md b/README.md new file mode 100644 index 0000000..929d974 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## System design for distributed systems + +This repo was built by claude code and codex for me before a system design interview diff --git a/src/lib/components/diagrams/ComparisonTable.svelte b/src/lib/components/diagrams/ComparisonTable.svelte index dd89f13..da3254a 100644 --- a/src/lib/components/diagrams/ComparisonTable.svelte +++ b/src/lib/components/diagrams/ComparisonTable.svelte @@ -11,25 +11,29 @@ interface Option { id: string; name: string; - color: string; - icon: string; + description?: string; + color?: string; + icon?: string; whenToUse: string[]; whenNotToUse: string[]; realWorldExample?: string; } interface Props { - title: string; + title?: string; subtitle?: string; options: Option[]; - features: ComparisonItem[]; + features?: ComparisonItem[]; } - let { title, subtitle = '', options, features }: Props = $props(); + let { title = '', subtitle = '', options, features = [] }: Props = $props(); let selectedOption = $state(null); - function getIcon(iconName: string) { - return (Icons as Record)[iconName] || Icons.Box; + type IconComponent = typeof Icons.Box; + + function getIcon(iconName?: string): IconComponent { + const iconMap = Icons as unknown as Record; + return (iconName ? iconMap[iconName] : null) ?? Icons.Box; } function getCellContent(value: string | boolean) { @@ -44,66 +48,104 @@
-
-

{title}

- {#if subtitle} -

{subtitle}

- {/if} -
+ {#if title || subtitle} +
+ {#if title} +

{title}

+ {/if} + {#if subtitle} +

{subtitle}

+ {/if} +
+ {/if} -
- - - - - {#each options as option} - {@const Icon = getIcon(option.icon)} - + + {#each features as feature, i} + + + {#each options as option} + {@const cell = getCellContent(feature.values[option.id])} + + {/each} + + {/each} + +
- Feature - selectedOption = selectedOption === option.id ? null : option.id} - > -
-
- -
- {option.name} -
+ {#if features.length > 0} +
+ + + + - {/each} - - - - {#each features as feature, i} - - {#each options as option} - {@const cell = getCellContent(feature.values[option.id])} - + {@const Icon = getIcon(option.icon)} + {/each} - {/each} - -
+ Feature
-
{feature.feature}
- {#if feature.description} -
{feature.description}
- {/if} -
- {#if cell.type === 'check'} - - {:else if cell.type === 'x'} - - {:else} - {cell.value} - {/if} - selectedOption = selectedOption === option.id ? null : option.id} + > +
+
+ +
+ {option.name} +
+
-
+
+
{feature.feature}
+ {#if feature.description} +
{feature.description}
+ {/if} +
+ {#if cell.type === 'check'} + + {:else if cell.type === 'x'} + + {:else} + {cell.value} + {/if} +
+
+ {:else} +
+ {#each options as option} + {@const Icon = getIcon(option.icon)} + {@const active = selectedOption === option.id} + + {/each} +
+ {/if} {#if selectedOption} diff --git a/src/lib/components/diagrams/DecisionTree.svelte b/src/lib/components/diagrams/DecisionTree.svelte index 941fb66..f596fbc 100644 --- a/src/lib/components/diagrams/DecisionTree.svelte +++ b/src/lib/components/diagrams/DecisionTree.svelte @@ -31,9 +31,11 @@ return nodes.find(n => n.id === id); } - function getIcon(iconName?: string) { - if (!iconName) return Icons.HelpCircle; - return (Icons as Record)[iconName] || Icons.HelpCircle; + type IconComponent = typeof Icons.Box; + + function getIcon(iconName?: string): IconComponent { + const iconMap = Icons as unknown as Record; + return (iconName ? iconMap[iconName] : null) ?? (Icons.HelpCircle as IconComponent); } function answer(nodeId: string, isYes: boolean) { diff --git a/src/lib/components/diagrams/GuidedWalkthrough.svelte b/src/lib/components/diagrams/GuidedWalkthrough.svelte index ede5d62..af2e97f 100644 --- a/src/lib/components/diagrams/GuidedWalkthrough.svelte +++ b/src/lib/components/diagrams/GuidedWalkthrough.svelte @@ -32,9 +32,11 @@ let isPlaying = $state(autoPlay); let intervalId: ReturnType | null = null; - function getIcon(iconName?: string) { - if (!iconName) return Icons.Circle; - return (Icons as Record)[iconName] || Icons.Circle; + type IconComponent = typeof Icons.Box; + + function getIcon(iconName?: string): IconComponent { + const iconMap = Icons as unknown as Record; + return (iconName ? iconMap[iconName] : null) ?? (Icons.Circle as IconComponent); } function nextStep() { @@ -202,6 +204,7 @@ {#each steps as _, i} {/each}
@@ -155,17 +172,17 @@

Worker Nodes

-

The muscle - runs your actual workloads

+

The muscle - runs your actual workloads

{#each nodeComponents as component} {/each}
@@ -177,12 +194,12 @@ {@const allComponents = [...controlPlaneComponents, ...nodeComponents]} {@const component = allComponents.find(c => c.id === selectedComponent)} {#if component} -
-

{component.name}

+
+

{component.name}

    {#each component.details as detail} -
  • - - +
  • + - {detail}
  • {/each} @@ -195,24 +212,24 @@
    -

    Control Plane Components

    +

    Control Plane Components

    {#each controlPlaneComponents as component} -
    +
    -

    {component.name}

    -

    {component.description}

    +

    {component.name}

    +

    {component.description}

    - + Control Plane
    {#each component.details as detail} -
    - +
    + {detail}
    {/each} @@ -224,10 +241,10 @@
    -

    How a Pod Gets Scheduled

    -

    Follow the journey from kubectl apply to running pod

    +

    How a Pod Gets Scheduled

    +

    Follow the journey from kubectl apply to running pod

    -
    +
    @@ -236,9 +253,9 @@
    {step.step}
    -
    -
    {step.title}
    -
    {step.desc}
    +
    +
    {step.title}
    +
    {step.desc}
    {/each} @@ -249,18 +266,18 @@
    -

    Pod Lifecycle

    +

    Pod Lifecycle

    -
    +
    {#each podLifecycle as phase, i}
    -
    -
    {phase.phase}
    -
    {phase.desc}
    +
    +
    {phase.phase}
    +
    {phase.desc}
    {#if i < podLifecycle.length - 1} -
    ->
    +
    ->
    {/if}
    {/each} @@ -270,68 +287,68 @@
    -

    Key Kubernetes Objects

    +

    Key Kubernetes Objects

    -
    +
    Pod
    -

    Smallest deployable unit. One or more containers that share network/storage.

    +

    Smallest deployable unit. One or more containers that share network/storage.

    -
    +
    Deployment
    -

    Manages ReplicaSets. Handles rolling updates and rollbacks.

    +

    Manages ReplicaSets. Handles rolling updates and rollbacks.

    -
    +
    Service
    -

    Stable network endpoint for pods. ClusterIP, NodePort, LoadBalancer.

    +

    Stable network endpoint for pods. ClusterIP, NodePort, LoadBalancer.

    -
    +
    ConfigMap
    -

    Non-sensitive configuration data. Injected as env vars or files.

    +

    Non-sensitive configuration data. Injected as env vars or files.

    -
    +
    Secret
    -

    Sensitive data like passwords, tokens. Base64 encoded (not encrypted!).

    +

    Sensitive data like passwords, tokens. Base64 encoded (not encrypted!).

    -
    +
    Ingress
    -

    HTTP routing rules. Path-based routing to Services.

    +

    HTTP routing rules. Path-based routing to Services.

    -
    +
    StatefulSet
    -

    For stateful apps. Stable network identity and persistent storage.

    +

    For stateful apps. Stable network identity and persistent storage.

    -
    +
    DaemonSet
    -

    Run pod on every node. Used for logging, monitoring agents.

    +

    Run pod on every node. Used for logging, monitoring agents.

    -
    +
    HPA
    -

    Horizontal Pod Autoscaler. Scale pods based on CPU/memory/custom metrics.

    +

    Horizontal Pod Autoscaler. Scale pods based on CPU/memory/custom metrics.

    -

    Kubernetes Networking Model

    +

    Kubernetes Networking Model

    -
    +

    Pod-to-Pod

    -

    Every pod gets a unique IP. Pods can communicate directly without NAT.

    -
    Implemented by CNI plugin (Calico, Cilium, etc.)
    +

    Every pod gets a unique IP. Pods can communicate directly without NAT.

    +
    Implemented by CNI plugin (Calico, Cilium, etc.)

    Pod-to-Service

    -

    Services provide stable virtual IPs (ClusterIP). kube-proxy routes to pods.

    -
    Uses iptables or IPVS rules
    +

    Services provide stable virtual IPs (ClusterIP). kube-proxy routes to pods.

    +
    Uses iptables or IPVS rules

    External-to-Service

    -

    NodePort, LoadBalancer, or Ingress expose services externally.

    -
    Ingress for HTTP, LoadBalancer for TCP/UDP
    +

    NodePort, LoadBalancer, or Ingress expose services externally.

    +
    Ingress for HTTP, LoadBalancer for TCP/UDP
    @@ -339,54 +356,54 @@
    -

    Production Best Practices

    +

    Production Best Practices

    -
    +

    DO

      -
    • +
    • + Set resource requests and limits on all pods
    • -
    • +
    • + Use liveness and readiness probes
    • -
    • +
    • + Run multiple replicas with anti-affinity
    • -
    • +
    • + Use namespaces for isolation
    • -
    • +
    • + Backup etcd regularly
    -
    +

    DON'T

      -
    • +
    • - Run workloads on control plane nodes
    • -
    • +
    • - Use :latest image tags in production
    • -
    • +
    • - Store secrets in ConfigMaps
    • -
    • +
    • - Skip pod disruption budgets
    • -
    • +
    • - Ignore resource quotas in multi-tenant clusters
    • diff --git a/src/routes/compute/serverless/+page.svelte b/src/routes/compute/serverless/+page.svelte index 2300cb2..950d731 100644 --- a/src/routes/compute/serverless/+page.svelte +++ b/src/routes/compute/serverless/+page.svelte @@ -53,40 +53,46 @@ title: 'Lambda Cold Start Flow', steps: [ { + id: 'cold-start-1', title: 'Invocation Request', - content: 'API Gateway triggers Lambda function', + description: 'API Gateway triggers Lambda function', details: 'A request arrives but no warm execution environment is available.', - tips: ['Cold starts affect ~1% of requests', 'More common during traffic spikes'] + tip: 'Cold starts affect ~1% of requests. More common during traffic spikes.' }, { + id: 'cold-start-2', title: 'Container Provisioning', - content: 'Lambda allocates compute resources', + description: 'Lambda allocates compute resources', details: 'AWS provisions a micro-VM with the configured memory. More memory = more CPU.', - tips: ['Memory range: 128MB - 10GB', 'CPU scales proportionally with memory'] + tip: 'Memory range: 128MB - 10GB. CPU scales proportionally with memory.' }, { + id: 'cold-start-3', title: 'Runtime Initialization', - content: 'Load Python/Node.js/Java runtime', + description: 'Load Python/Node.js/Java runtime', details: 'The language runtime is loaded and initialized in the container.', - tips: ['Python/Node.js: ~100-200ms', 'Java: ~500ms-1s (JVM startup)'] + tip: 'Python/Node.js: ~100-200ms. Java: ~500ms-1s (JVM startup).' }, { + id: 'cold-start-4', title: 'Function Code Loading', - content: 'Download and extract deployment package', + description: 'Download and extract deployment package', details: 'Your code is downloaded from S3 and extracted. Larger packages take longer.', - tips: ['Keep deployment package small', 'Use Lambda Layers for shared code'] + tip: 'Keep deployment package small. Use Lambda Layers for shared code.' }, { + id: 'cold-start-5', title: 'Static Initialization', - content: 'Run code outside handler (global scope)', + description: 'Run code outside handler (global scope)', details: 'Code at module level runs once per container. Initialize DB connections here.', - tips: ['Initialize SDK clients outside handler', 'This code runs once per container lifecycle'] + tip: 'Initialize SDK clients outside handler. This code runs once per container lifecycle.' }, { + id: 'cold-start-6', title: 'Handler Execution', - content: 'Your function code runs', + description: 'Your function code runs', details: 'Finally, your handler function executes. Container is now "warm".', - tips: ['Subsequent invocations skip steps 1-5', 'Warm container reused for ~15 minutes'] + tip: 'Subsequent invocations skip steps 1-5. Warm container reused for ~15 minutes.' } ] }; diff --git a/src/routes/databases/caching/+page.svelte b/src/routes/databases/caching/+page.svelte index 3b0cfb4..d20e242 100644 --- a/src/routes/databases/caching/+page.svelte +++ b/src/routes/databases/caching/+page.svelte @@ -159,40 +159,46 @@ title: 'Cache-Aside Pattern Flow', steps: [ { + id: 'cache-aside-1', title: 'Application Requests Data', - content: 'User requests user profile for user_id=123', + description: 'User requests user profile for user_id=123', details: 'The application receives a request that needs data from the database.', - tips: ['Common entry point: API endpoint, service method'] + tip: 'Common entry point: API endpoint, service method.' }, { + id: 'cache-aside-2', title: 'Check Cache First', - content: 'GET user:123 from Redis', + description: 'GET user:123 from Redis', details: 'Application checks if the data exists in cache before hitting the database.', - tips: ['Use consistent key naming: entity:id', 'Handle cache connection failures gracefully'] + tip: 'Use consistent key naming: entity:id. Handle cache connection failures gracefully.' }, { + id: 'cache-aside-3', title: 'Cache Hit - Return Immediately', - content: 'Data found! Return cached user profile', + description: 'Data found! Return cached user profile', details: 'If data is in cache, return it immediately. This is the fast path.', - tips: ['Typical cache hit latency: < 1ms', 'Log cache hits for monitoring'] + tip: 'Typical cache hit latency: < 1ms. Log cache hits for monitoring.' }, { + id: 'cache-aside-4', title: 'Cache Miss - Query Database', - content: 'SELECT * FROM users WHERE id = 123', + description: 'SELECT * FROM users WHERE id = 123', details: 'If cache miss, query the primary database for the data.', - tips: ['This is the slow path', 'Consider query optimization'] + tip: 'This is the slow path. Consider query optimization.' }, { + id: 'cache-aside-5', title: 'Store in Cache', - content: 'SET user:123 with TTL 3600 seconds', + description: 'SET user:123 with TTL 3600 seconds', details: 'After getting data from DB, store it in cache for future requests.', - tips: ['Always set a TTL to prevent stale data', 'Consider cache size limits'] + tip: 'Always set a TTL to prevent stale data. Consider cache size limits.' }, { + id: 'cache-aside-6', title: 'Return Data to Caller', - content: 'Return user profile to client', + description: 'Return user profile to client', details: 'Finally return the data. Next request will hit the cache.', - tips: ['Monitor cache hit rate', 'Aim for > 90% hit rate'] + tip: 'Monitor cache hit rate. Aim for > 90% hit rate.' } ] }; diff --git a/src/routes/decisions/which-database/+page.svelte b/src/routes/decisions/which-database/+page.svelte index 77cee28..4bf6cfe 100644 --- a/src/routes/decisions/which-database/+page.svelte +++ b/src/routes/decisions/which-database/+page.svelte @@ -306,30 +306,30 @@

      By Use Case

      {#each [ - { case: 'E-commerce orders', db: 'PostgreSQL', color: 'blue' }, - { case: 'User profiles', db: 'MongoDB', color: 'green' }, - { case: 'Session caching', db: 'Redis', color: 'red' }, - { case: 'IoT sensor data', db: 'TimescaleDB', color: 'purple' }, - { case: 'Social graph', db: 'Neo4j', color: 'pink' } + { case: 'E-commerce orders', db: 'PostgreSQL' }, + { case: 'User profiles', db: 'MongoDB' }, + { case: 'Session caching', db: 'Redis' }, + { case: 'IoT sensor data', db: 'TimescaleDB' }, + { case: 'Social graph', db: 'Neo4j' } ] as item}
      {item.case} - {item.db} + {item.db}
      {/each}

      By Data Type

      {#each [ - { type: 'Relational', db: 'PostgreSQL/MySQL', color: 'blue' }, - { type: 'Documents', db: 'MongoDB/Firestore', color: 'green' }, - { type: 'Key-Value', db: 'Redis/DynamoDB', color: 'yellow' }, - { type: 'Time-Series', db: 'TimescaleDB/InfluxDB', color: 'purple' }, - { type: 'Graph', db: 'Neo4j/Neptune', color: 'pink' } + { type: 'Relational', db: 'PostgreSQL/MySQL' }, + { type: 'Documents', db: 'MongoDB/Firestore' }, + { type: 'Key-Value', db: 'Redis/DynamoDB' }, + { type: 'Time-Series', db: 'TimescaleDB/InfluxDB' }, + { type: 'Graph', db: 'Neo4j/Neptune' } ] as item}
      {item.type} - {item.db} + {item.db}
      {/each}
      diff --git a/src/routes/fundamentals/cap-theorem/+page.svelte b/src/routes/fundamentals/cap-theorem/+page.svelte index fc2265d..118a5e5 100644 --- a/src/routes/fundamentals/cap-theorem/+page.svelte +++ b/src/routes/fundamentals/cap-theorem/+page.svelte @@ -43,6 +43,7 @@ description: 'NOT possible in distributed systems', explanation: 'In a distributed system, network partitions are inevitable. When a partition occurs, you must choose between C and A.', examples: ['Single-node databases (PostgreSQL, MySQL on one server)', 'Not achievable in distributed systems'], + useCase: '', color: '#94A3B8', viable: false }, @@ -110,7 +111,9 @@ selectCombination('CA')} - onmouseenter={() => {}} + role="button" + tabindex="0" + onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && selectCombination('CA')} > selectCombination('CP')} + role="button" + tabindex="0" + onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && selectCombination('CP')} > selectCombination('AP')} + role="button" + tabindex="0" + onkeydown={(e) => (e.key === 'Enter' || e.key === ' ') && selectCombination('AP')} > hoveredVertex = key as 'C' | 'A' | 'P'} onmouseleave={() => hoveredVertex = null} > @@ -200,6 +210,7 @@ {#each Object.entries(vertices) as [key, vertex]}