Implement a Simplified DNS Resolver
Company: Anthropic
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Take-home Project
Implement a simplified DNS resolver in Python. You are given an in-memory DNS zone and must complete a small resolver step by step. The goal is not to build a real network DNS server, but to correctly handle name normalization, aliases, fallback behavior, errors, cycles, caching, and safe concurrent access.
### Data model
A DNS zone is represented as a dictionary from domain name to a record object:
```python
zone = {
"example.com": {"A": ["93.184.216.34"]},
"www.example.com": {"CNAME": "example.com"},
"api.example.com": {"A": ["10.0.0.5", "10.0.0.6"]},
}
```
Each record may contain:
- `A`: a list of IPv4 address strings.
- `CNAME`: a single alias target domain.
Assume a record will not intentionally contain both `A` and `CNAME`, but your code should handle malformed records gracefully.
### Required API
Implement:
```python
class DNSResolver:
def __init__(self, zone: dict, fallback=None):
pass
def resolve(self, domain: str) -> list[str]:
pass
```
`fallback`, if provided, is another object with a compatible `resolve(domain: str) -> list[str]` method.
### Requirements
1. **Normalize domain names**
- Domain lookup should be case-insensitive.
- Ignore leading and trailing whitespace.
- Treat a trailing dot as optional.
- For example, `" WWW.Example.COM. "` should resolve the same way as `"www.example.com"`.
2. **Resolve direct A records**
- If a domain has an `A` record, return its list of IP addresses in stored order.
- Return a new list so callers cannot mutate internal resolver state.
3. **Resolve aliases**
- If a domain has a `CNAME`, follow the alias chain until an `A` record is found.
- For example, if `www.example.com -> example.com` and `example.com` has an `A` record, resolving `www.example.com` should return the IPs for `example.com`.
4. **Handle missing records**
- If the domain is not found locally and no fallback resolver exists, raise `LookupError`.
- If a fallback resolver exists, delegate to it.
5. **Handle invalid records**
- If a record has neither `A` nor `CNAME`, raise `ValueError`.
- If a record has a malformed `A` value, such as a string instead of a list, raise `ValueError`.
- If a record has a malformed `CNAME` value, raise `ValueError`.
6. **Detect alias cycles**
- If aliases form a cycle, raise `RuntimeError`.
- Example: `a.com -> b.com -> c.com -> a.com`.
7. **Add caching**
- Cache successful local resolutions so repeated calls avoid recomputing the same alias chain.
- The cache key should use the normalized domain name.
- Do not cache failures unless explicitly documented.
- Returned cached lists must still be safe from caller mutation.
8. **Support concurrent calls**
- Multiple threads may call `resolve` at the same time.
- Ensure the cache and other mutable internal state remain consistent.
- Avoid holding locks while calling the fallback resolver if possible.
### Examples
```python
zone = {
"example.com": {"A": ["93.184.216.34"]},
"www.example.com": {"CNAME": "example.com"},
"loop1.com": {"CNAME": "loop2.com"},
"loop2.com": {"CNAME": "loop1.com"},
}
resolver = DNSResolver(zone)
resolver.resolve(" example.COM. ")
# returns ["93.184.216.34"]
resolver.resolve("www.example.com")
# returns ["93.184.216.34"]
resolver.resolve("missing.com")
# raises LookupError
resolver.resolve("loop1.com")
# raises RuntimeError
```
Design the implementation so that it can be tested incrementally with unit tests for each requirement.
Quick Answer: This question evaluates understanding of DNS semantics (name normalization, A and CNAME records, alias chains and cycle detection), robust error handling, cache design, and safe concurrent access when implementing a simplified resolver.