Implement a Fetcher class with get(id) and post(id, x) methods that throw on missing/duplicate ids
A `Fetcher` with an internal `Map<id, value>`. `get(id)` throws if id absent. `post(id, x)` throws if id already exists; otherwise stores. Tests the basics of class state + invariants. Extensions: `update`/`delete`, custom error types, async simulation, typed generics, eviction.
A small but precise problem: implement a class with two methods that enforce invariants (no missing reads, no duplicate writes). The interview tests whether you write tight code and handle errors deliberately.
The core implementation
class FetcherError extends Error {
constructor(message: string, public code: string) {
super(message);
this.name = "FetcherError";
}
}
class Fetcher<T = unknown> {
private store = new Map<string, T>();
get(id: string): T {
if (!this.store.has(id)) {
throw new FetcherError(`Missing id: ${id}`, "NOT_FOUND");
}
return this.store.get(id) as T;
}
post(id: string, value: T): void {
if (this.store.has(id)) {
throw new FetcherError(`Duplicate id: ${id}`, "DUPLICATE");
}
this.store.set(id, value);
}
}Why these choices
Map, not plain object
- O(1)
has/get/setfor arbitrary keys. - Doesn't conflict with prototype keys (
__proto__,constructor). - Honest iteration order.
Custom error class
Distinguishes the fetcher's domain errors from generic Error. Callers can check err instanceof FetcherError or err.code. Avoid throwing string literals or mixing in arbitrary Error shapes.
Throw vs return
The spec says throw — do that. If the caller wants a "soft" API, layer it:
function tryGet(fetcher, id) {
try { return { ok: true, value: fetcher.get(id) }; }
catch (e) { return { ok: false, error: e }; }
}Extensions to mention
update / delete
update(id: string, value: T): void {
if (!this.store.has(id)) throw new FetcherError(`Missing id: ${id}`, "NOT_FOUND");
this.store.set(id, value);
}
delete(id: string): void {
if (!this.store.has(id)) throw new FetcherError(`Missing id: ${id}`, "NOT_FOUND");
this.store.delete(id);
}Async variant
If the spec is "simulate a remote fetcher":
async get(id: string): Promise<T> {
await delay(20);
if (!this.store.has(id)) throw new FetcherError(`Missing id: ${id}`, "NOT_FOUND");
return this.store.get(id) as T;
}TTL / eviction
private timers = new Map<string, NodeJS.Timeout>();
post(id: string, value: T, ttlMs?: number) {
if (this.store.has(id)) throw new FetcherError(...);
this.store.set(id, value);
if (ttlMs) {
this.timers.set(id, setTimeout(() => this.store.delete(id), ttlMs));
}
}upsert
A separate method, not a flag — different semantics.
Tests to mention
- get on missing → throws specific error.
- post on duplicate → throws.
- post then get → returns the stored value.
- post → update → get → returns new value.
- ids must be the right type (TS catches this; runtime — guard).
Pitfalls
- Throwing strings instead of Error subclasses — loses stack traces.
- Using plain object {} as store — prototype pollution.
- Returning undefined on missing instead of throwing — violates the spec.
- Mutating value after post — caller reference still mutates the internal copy; if defensive, clone.
Interview framing
"A class with a Map<id, value> internal store. get throws a custom error if the id isn't present; post throws if it already is. Map (not plain object) for O(1) has/get/set and to avoid prototype-key conflicts. Custom error class so callers can distinguish domain errors from generic Error. Typed generic over the value. Extensions to discuss if asked: update/delete, async variant simulating a remote fetcher, TTL/eviction, and a soft-API wrapper that returns {ok, value/error} instead of throwing for callers who want it."
Follow-up questions
- •Why use Map instead of a plain object?
- •Why a custom error class?
- •How would you make it async?
- •How would you add TTL/eviction?
Common mistakes
- •Plain object store with prototype-key risk.
- •Throwing strings.
- •Returning undefined on missing instead of throwing.
- •No type safety on the value.
Performance considerations
- •O(1) operations via Map. For very high cardinality, watch memory; add eviction.
Edge cases
- •post with the same id and same value — still throw (spec says duplicate).
- •get/post on empty-string id — should reject if invalid?
- •Concurrent calls (in async variant).
Real-world examples
- •In-memory cache primitive, test doubles for an API client.