← Ziphq Interview Insights

Ziphq·Software Engineer·Onsite - System Design / Architecture·Senior

Senior
Jun 2026

Summary

ZipHQ system design round for a Software Engineer role, and they threw a multi-level in-memory database problem at me that kept escalating. Each level unlocked only after passing tests on the previous one, which was a nice touch but also meant you couldn't skip ahead if you got stuck.

Questions Asked (4)

Q1

Design the basic CRUD operations for an in-memory database: create and delete records, upsert and delete fields within a record, read an entire record or a specific field. How do you handle non-existent records or fields, and what are the idempotency guarantees?

System DesignAPI & IntegrationsAlgorithms & Data Structures
Author's notes

This felt like a warmup but the idempotency piece tripped me up a bit.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the data model and API surface, then systematically define each operation's semantics, including error handling and idempotency. Use a simple in-memory structure like a hash map and discuss trade-offs for concurrency and consistency.

Pro tip: Explicitly state your assumptions about record/field existence and idempotency upfront, and tie them to real-world use cases like retry-safe APIs. This shows you think about production reliability, not just algorithms.

1. Clarify Requirements and Data Model

Ask about expected operations, data types, concurrency needs, and whether records are identified by keys. Define a simple model: a map from record IDs to records (maps of field names to values).

2. Define CRUD Operations and Semantics

Specify each operation: create (insert if absent, error if exists), delete record (remove if exists, no-op if absent), upsert field (set field value, create record if needed), delete field (remove field if exists, no-op if absent), read record (return full record or error if absent), read field (return value or error if absent).

3. Handle Non-Existent Records/Fields

Decide on error handling: for reads, return a not-found error; for deletes, make them idempotent (no error if absent); for upserts, create as needed. Discuss whether to use exceptions, error codes, or optional returns.

4. Discuss Idempotency Guarantees

Explain which operations are idempotent: delete record/field (repeated calls have same effect), upsert (repeated calls set same value), create (not idempotent if it errors on existing). Mention how idempotency aids retry logic in distributed systems.

5. Address Concurrency and Edge Cases

Mention thread-safety (e.g., using locks or concurrent data structures), atomicity of compound operations, and potential race conditions. Discuss memory management and scalability limits of in-memory storage.

Key Points to Mention

  • Choice of data structure (e.g., hash map) and its time complexity for operations.
  • Idempotency of delete and upsert operations, and non-idempotency of create.
  • Error handling strategies for missing records/fields (exceptions vs. error codes).
  • Concurrency control (locks, atomic operations) for thread safety.
  • API design considerations: return values, error types, and documentation.
  • Trade-offs between strictness (error on missing) and leniency (no-op) for operations.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Add a filtered field listing feature: given a record ID and a filter expression, return only the fields that match. How do you define the filter format, and what comparison types do you support?

System DesignAPI & IntegrationsTechnical Trade-offs
Author's notes

I fumbled the filter expression format initially, tried to invent some JSON query syntax on the fly and then backed into something simpler.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying the requirements: what types of fields exist, what filter operations are needed, and how the API will be used. Then propose a simple, extensible filter format (e.g., JSON-based) and enumerate supported comparison types, justifying your choices with trade-offs. Finally, discuss implementation considerations like validation, performance, and security.

Pro tip: Emphasize that the filter format should be versioned and backward-compatible to avoid breaking clients as new comparison types are added. Also, mention that you'd start with a minimal set of operators and expand based on user feedback to avoid over-engineering.

1. Clarify requirements and constraints

Ask about the expected use cases, field types, performance needs, and whether the filter will be used internally or exposed to external clients. This ensures your design aligns with actual needs.

2. Define the filter format

Propose a structured format, such as a JSON object with field, operator, and value, or a more complex expression tree. Explain why you chose it (e.g., readability, extensibility, ease of parsing).

3. Enumerate supported comparison types

List operators like equality, inequality, greater than, less than, contains, starts with, in, etc. Group them by data type (string, number, date, boolean) and justify each based on common filtering needs.

4. Discuss implementation and trade-offs

Explain how you would parse and evaluate the filter, handle errors, and ensure security (e.g., prevent injection). Mention performance considerations like indexing or pushing filters to the database.

5. Address extensibility and versioning

Describe how new operators or field types can be added without breaking existing clients, such as using a versioned schema or feature flags.

Key Points to Mention

  • Filter format options: simple key-operator-value pairs vs. nested expression trees, with trade-offs.
  • Comparison types: equality, inequality, range (>, <, >=, <=), string matching (contains, startsWith, endsWith), set membership (in, notIn), and null checks.
  • Data type handling: how operators apply differently to strings, numbers, dates, booleans, and arrays.
  • Security: input validation, sanitization, and avoiding injection attacks when translating filters to queries.
  • Performance: indexing, query optimization, and limiting filter complexity to prevent abuse.
  • API design: versioning, error responses, and documentation for the filter format.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

Extend the database to support per-record TTL so records automatically expire after a set duration. How do you model the time source, check for expiry, and handle reads or writes on an already-expired record? What's your target complexity for TTL updates?

System DesignAlgorithms & Data StructuresTechnical Trade-offs
Author's notes

Min-heap for expiry tracking, O(log n) per insert or update.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements: is TTL per-record, and what are the read/write patterns? Then propose a design that stores an expiration timestamp per record, uses a monotonic time source, and lazily checks expiry on access, with a background sweeper for cleanup. Discuss trade-offs between lazy and eager expiration, and target O(1) for TTL updates.

Pro tip: Mention that using wall-clock time can cause issues with clock skew and NTP adjustments; a monotonic clock is safer for measuring durations, but for absolute expiration you need a consistent time source across nodes. Also, consider that TTL updates should be O(1) by simply updating the expiration timestamp field.

1. Clarify requirements and constraints

Ask about TTL granularity, expected read/write ratio, consistency requirements, and whether expiration must be exact or eventual. This shapes the design.

2. Model the time source

Use a monotonic clock for measuring durations to avoid clock skew, but for absolute expiration across distributed nodes, use a synchronized wall-clock (e.g., NTP) or a central time service. Store expiration as an absolute timestamp.

3. Design expiry checking and handling

On read, check if the record's expiration timestamp is in the past; if so, treat as expired (return not found or delete). On write, if updating an expired record, decide whether to resurrect it or treat as new. Use lazy deletion plus a background sweeper to reclaim space.

4. Discuss TTL update complexity

Updating TTL should be O(1) by simply overwriting the expiration timestamp. If using a priority queue for expiration, updates may require O(log n) unless using a more advanced structure like a timing wheel.

5. Address trade-offs and edge cases

Compare lazy vs. eager expiration: lazy saves CPU but may return expired data if not checked; eager uses more resources but ensures timely cleanup. Discuss handling of clock skew, time zone issues, and atomicity of check-and-delete.

Key Points to Mention

  • Use of monotonic vs. wall-clock time and implications for distributed systems
  • Lazy expiration on read/write vs. background sweeper for cleanup
  • O(1) TTL updates by storing expiration timestamp per record
  • Handling of already-expired records on read (return not found) and write (treat as new or reject)
  • Trade-offs between precision of expiration and system overhead
  • Potential use of timing wheels or hierarchical timing wheels for efficient expiration

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

Add support for historical queries: given a record ID and a past timestamp, return the state of the record as it existed at that point in time. How do creates, updates, deletes, and TTL interact with these look-back reads? What storage approach do you use and what are the trade-offs?

System DesignData ModelingTechnical Trade-offs
Author's notes

This is where things got hard.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

Start by clarifying requirements: query patterns, latency needs, retention period, and consistency expectations. Then propose a storage design that captures every change as an immutable, time-stamped version (e.g., append-only log or bitemporal table), and explain how creates, updates, deletes, and TTL map to versioned records. Finally, discuss trade-offs between storage cost, query performance, and complexity, and mention optimizations like indexing and compaction.

Pro tip: Emphasize that deletes and TTL expirations must be modeled as explicit versioned events (tombstones or expiration markers) rather than physical removals, otherwise historical queries would incorrectly show records as missing. Also, mention that you would validate the design against real query patterns to avoid over-engineering.

1. Clarify requirements and constraints

Ask about query frequency, acceptable latency, retention period, and whether reads must be strongly consistent. This shapes the choice of storage and indexing strategy.

2. Design versioned storage model

Propose an append-only store where each change (create, update, delete, TTL expiry) writes a new version with a timestamp and record ID. Use a composite key like (record_id, timestamp) for efficient look-back queries.

3. Define semantics for each operation

Explain how creates, updates, deletes, and TTL are represented: creates insert initial version; updates insert new version; deletes insert a tombstone; TTL inserts an expiration marker. Historical reads return the latest version with timestamp <= query time, treating tombstones/expirations as absence.

4. Discuss trade-offs and optimizations

Compare storage cost vs. query performance: append-only increases storage but enables fast look-back with proper indexing. Mention compaction, tiered storage, and caching to mitigate costs. Also address consistency and concurrency.

5. Summarize and validate

Recap the design, highlight how it meets requirements, and suggest validating with real query patterns and load testing. Mention potential extensions like bitemporal modeling if needed.

Key Points to Mention

  • Append-only/versioned storage with composite key (record_id, timestamp) for efficient range scans.
  • Tombstones for deletes and explicit expiration markers for TTL to preserve historical accuracy.
  • Query semantics: return latest version with timestamp <= query time; if tombstone/expiration, return not found.
  • Trade-offs: storage growth vs. query latency; indexing and compaction strategies.
  • Consistency and concurrency: use of timestamps or version numbers to order writes.
  • Alternatives: bitemporal tables, event sourcing, or snapshot-based approaches and their pros/cons.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.