Design Walkthrough
Problem Statement
The Question: Design a system that validates whether products are compatible with each other, like PCPartPicker for computer builds or AutoZone for car parts.
Real-world examples: - PCPartPicker: Will this RAM work with this motherboard? Does the GPU fit in this case? - AutoZone: Does this oil filter fit my 2019 Honda Civic? - Dell Configurator: Can I add this GPU to this laptop model? - IKEA: Will this countertop work with this cabinet frame?
What to say first
Before I design, let me understand the compatibility domain. Are we talking about PC parts, automotive, furniture, or a general-purpose system? The domain significantly affects the data model.
Hidden requirements interviewers test: - Can you model complex relationships (direct, transitive, conditional)? - Do you understand the difference between positive and negative compatibility? - Can you handle rule conflicts and priorities? - How do you keep compatibility data accurate over time?
Clarifying Questions
These questions shape your entire data model.
Question 1: Compatibility Domain
What types of products? PC components, automotive parts, or general eCommerce?
Why this matters: PC parts have well-defined specs (socket types, form factors). Car parts use Year/Make/Model. General products need flexible attributes. Typical answer: PC parts for this interview (most structured example) Architecture impact: Can use spec-based matching rather than explicit part-to-part rules
Question 2: Compatibility Types
Is compatibility binary (yes/no) or graduated (perfect fit, works with adapter, not recommended)?
Why this matters: Graduated compatibility needs scoring and thresholds. Typical answer: Primarily binary, but flag warnings for suboptimal combinations Architecture impact: Need compatibility scores, not just boolean
Question 3: Rule Source
Where does compatibility data come from? Manufacturer specs, user reports, manual curation?
Why this matters: Affects data accuracy and update frequency. Typical answer: Mix of manufacturer specs (authoritative) and user reports (crowdsourced) Architecture impact: Need confidence scores and conflict resolution
Question 4: Scale
How many products? How many compatibility checks per second?
Why this matters: Determines if we can pre-compute or must evaluate in real-time. Typical answer: 100K products, 10K compatibility checks per second Architecture impact: Can pre-compute common combinations, cache aggressively
Stating assumptions
I will assume: PC parts domain with 100K products, spec-based compatibility rules, binary compatibility with warnings, 10K checks/second, mix of manufacturer specs and user reports.
The Hard Part
Say this out loud
The hard part is modeling compatibility relationships that can be direct (A works with B), transitive (A works with B, B works with C, so A works with C), and conditional (A works with B only if B has feature X).
Why this is genuinely hard:
- 1.Combinatorial Explosion: 100K products means 10 billion possible pairs. You cannot store explicit compatibility for every pair.
- 2.Multiple Compatibility Dimensions: A CPU and motherboard must match on socket, chipset support, power requirements, BIOS version, and more. Failure on ANY dimension means incompatible.
- 3.Transitive Relationships: If RAM-A works with Motherboard-B, and Motherboard-B works with CPU-C, does that tell us anything about RAM-A and CPU-C? Sometimes yes, sometimes no.
- 4.Negative vs Positive Rules: Do you store what IS compatible or what IS NOT compatible? Different trade-offs.
- 5.Rule Conflicts: Manufacturer says compatible, users report issues. Which wins?
Common mistake
Candidates often try to store explicit part-to-part compatibility. With 100K products, that is 10 billion pairs - impossible. You must use attribute-based rules.
The fundamental insight:
Instead of storing: "Part A compatible with Part B"
Store: "Parts with attribute X=value1 compatible with parts with attribute Y=value2"
This reduces billions of pairs to thousands of rules.
Part-based (BAD):
CPU-i9-13900K compatible with MB-ASUS-Z790
CPU-i9-13900K compatible with MB-MSI-Z790
CPU-i9-13900K compatible with MB-Gigabyte-Z790
... (millions of entries)
Rule-based (GOOD):
CPUs with socket=LGA1700 compatible with Motherboards with socket=LGA1700
... (thousands of rules)Scale and Access Patterns
Let me estimate the scale and understand access patterns.
| Dimension | Value | Impact |
|---|---|---|
| Products | 100,000 | Cannot store explicit N^2 compatibility pairs |
| Product Categories | 20 (CPU, GPU, RAM, etc.) | Compatibility only within/across certain categories |
What to say
At 100K products, we cannot store explicit compatibility. But a cart has at most 15 items, so we only need to evaluate 100 pair-wise checks per request. This is tractable with rule-based evaluation.
Access Pattern Analysis:
- Read-heavy: Compatibility checks far exceed product/rule updates - Batch validation: Check entire cart, not single items - Incremental updates: When user adds item, only check new item against existing - Category-scoped: CPU only needs checking against motherboard, cooler, not against mouse, keyboard
Cart validation:
- Cart size: 10 items average
- Pairs to check: 10 * 9 / 2 = 45 pairsHigh-Level Architecture
Let me design the system architecture.
What to say
I will separate the system into three layers: Product Catalog (stores products and attributes), Rules Engine (defines compatibility logic), and Validation Service (evaluates compatibility in real-time).
Parts Compatibility Architecture
Component Responsibilities:
- 1.Validation Service: Stateless nodes that evaluate compatibility - Loads rules and product attributes into memory - Evaluates rule set for each product pair - Returns compatibility result with reasons
- 2.Rule Cache: In-memory cache (Redis) of rules and frequently accessed products - Reduces database load - Sub-millisecond rule lookup
- 3.PostgreSQL: Source of truth for products and attributes - JSONB for flexible attribute storage - Strong consistency for admin updates
- 4.Neo4j (Optional): Graph database for complex relationship queries - Useful for finding all compatible products - Transitive relationship traversal
- 5.Admin Tools: UI for managing rules and reviewing conflicts
Real-world reference
PCPartPicker uses a PostgreSQL-based system with heavy caching. They pre-compute compatibility for popular combinations and evaluate rules in real-time for less common builds.
Data Model and Storage
The data model is the most critical part of this system.
What to say
I will model products as entities with typed attributes, and compatibility as rules that operate on those attributes. This gives us flexibility without combinatorial explosion.
-- Product categories define what attributes are relevant
CREATE TABLE categories (
id SERIAL PRIMARY KEY,-- Compatibility rules between category pairs
CREATE TABLE compatibility_rules (
id SERIAL PRIMARY KEY,Rule Types and Examples:
// Type 1: Attribute Match
// CPU socket must match motherboard socket
{Important detail
Store attributes as JSONB, not as separate columns. Products have vastly different attributes - a CPU has socket and cores, RAM has speed and capacity, cases have dimensions. JSONB handles this heterogeneity.
Algorithm Deep Dive
Let me walk through the compatibility validation algorithm.
Validation Flow
class CompatibilityValidator:
def __init__(self, rule_cache, product_cache):
self.rules = rule_cachedef evaluate_rule(self, rule: Rule, source: Product, target: Product) -> RuleResult:
"""Evaluate a single compatibility rule."""
Optimization: Early termination
For blocking rules, you can return immediately on first failure. For best UX, collect all issues so users see everything wrong at once, not one error at a time.
Consistency and Invariants
System Invariants
The system must NEVER allow checkout of incompatible parts when a blocking rule fails. False positives (allowing incompatible) cause returns and customer anger. False negatives (blocking compatible) are annoying but recoverable.
Consistency Requirements:
- 1.Rule Consistency: If rule says A incompatible with B, same rule should say B incompatible with A (symmetric)
- 2.Transitive Consistency: If we claim transitive compatibility, verify the chain is actually transitive
- 3.Version Consistency: Product attributes and rules must be in sync - stale cache can cause wrong results
| Scenario | Risk | Mitigation |
|---|---|---|
| Stale rule cache | New incompatibility not enforced | TTL + cache invalidation on rule update |
| Missing product attribute | Cannot evaluate rule | Fail safe - treat as warning, not pass |
| Conflicting rules | One says yes, one says no | Priority system, higher priority wins |
| Rule update mid-checkout | Different results for same cart | Snapshot rules at cart creation time |
Business impact mapping
A customer who receives incompatible parts will return them (15-30% restocking cost), leave bad reviews, and possibly never return. The cost of a false positive is 10-100x the annoyance of a false negative.
def resolve_rule_conflicts(rules: list[Rule], source: Product, target: Product) -> bool:
"""
When multiple rules give conflicting results:Failure Modes and Resilience
Proactively discuss failures
Let me walk through failure modes. The key decision is whether to fail open (allow checkout) or fail closed (block checkout) on errors.
| Failure | Impact | Mitigation | Fail Mode |
|---|---|---|---|
| Rule cache down | Cannot evaluate rules | Local cache fallback + circuit breaker | Fail CLOSED - block checkout |
| Product DB slow | Slow validation | Aggressive caching + timeout | Fail CLOSED with message |
| Rule evaluation error | Specific rule crashes | Try-catch per rule, log error | Skip rule with warning |
| Missing product data | Cannot find product in cart | Return error immediately | Fail CLOSED |
| Network partition | Cannot reach data stores | Local read replica | Fail CLOSED |
Critical: Fail CLOSED
Unlike rate limiting where we fail open, compatibility MUST fail closed. Allowing incompatible parts through causes real business harm (returns, refunds, angry customers). It is better to block a valid checkout than allow an invalid one.
def validate_cart_safe(self, product_ids: list[str]) -> ValidationResult:
"""
Fail-safe validation that blocks on any error.Graceful Degradation:
If the validation service is overloaded:
- 1.Queue requests: Add to queue, return async result 2. Cache recent results: If same cart validated recently, return cached 3. Reduce rule evaluation: Skip low-priority warning rules 4. Circuit breaker: If error rate > 10%, return generic error
Evolution and Scaling
What to say
This design handles 100K products and 10K checks/second easily. Let me discuss how it evolves for 10x scale and additional features.
Evolution Path:
Stage 1: Simple Rules (Current Design) - Attribute-based rules - PostgreSQL + Redis cache - Works up to 100K products, 10K checks/sec
Stage 2: Pre-computed Compatibility - Pre-compute compatibility for popular product pairs - Store in lookup table: (product_a, product_b) -> compatible - Fallback to rule evaluation for uncommon pairs
Stage 3: Graph-Based Model - Use Neo4j for complex relationship queries - Find all compatible products for a given item - Transitive compatibility through graph traversal
Graph-Based Compatibility Model
Additional Features:
| Feature | How to Implement | Complexity |
|---|---|---|
| Find compatible products | Graph query: MATCH (p)-[:COMPATIBLE]-(q) WHERE p.id = X | Medium |
| Compatibility score | Sum of rule confidence * rule weight | Low |
| Upgrade suggestions | Find products with superset compatibility | High |
| Build templates | Pre-validated product combinations | Medium |
| User-reported issues | Crowdsourced negative rules with low confidence | Medium |
Alternative approach
If the domain were automotive parts (Year/Make/Model), I would use a different model - explicit fitment data from manufacturers rather than attribute-based rules. Car parts have well-defined fitment databases that map parts to vehicle IDs.
What I would do differently for...
Automotive parts: Use fitment database approach. Parts have explicit vehicle ID lists. Query is simple lookup, not rule evaluation.
Furniture (IKEA-style): Model as assembly graph. A table needs legs + top. Legs need screws. Track which components satisfy which requirements.
Enterprise hardware: Add capacity planning. Not just does it fit, but does the configuration have enough compute/storage/network for the workload.