Coinbase OA Interview Question: Sandbox Tests | In-Memory Database Field Queries, TTL, Backup & Restore

22 Views
No Comments

Level 1

The basic level of the in-memory database contains records. Each record can be accessed with a unique identifier key of string type. A record may contain several fieldvalue pairs, both of which are of string type.

  • set(self, key: str, field: str, value: str) -> None β€” should insert a fieldvalue pair to the record associated with key. If the field in the record already exists, replace the existing value with the specified value. If the record does not exist, create a new one.
  • get(self, key: str, field: str) -> str | None β€” should return the value contained within field of the record associated with key. If the record or the field doesn’t exist, should return None.
  • delete(self, key: str, field: str) -> bool β€” should remove the field from the record associated with key. Returns True if the field was successfully deleted, and False if the key or the field do not exist in the database.

Examples

The example below shows how these operations should work (the section is scrollable to the right):

Level 2

The database should support displaying data based on filters. Introduce an operation to support printing some fields of a record.

  • scan(self, key: str) -> list[str] β€” should return a list of strings representing the fields of a record associated with key. The returned list should be in the following format ["<field_1>(<value_1>)", ...], where fields are sorted lexicographically. If the specified record does not exist, returns an empty list.
  • scan_by_prefix(self, key: str, prefix: str) -> list[str] β€” should return a list of strings representing some fields of a record associated with key. Specifically, only fields that start with prefix should be included. The returned list should be in the same format as in the scan operation with fields sorted in lexicographical order.

Examples

The example below shows how these operations should work (the section is scrollable to the right):

Level 3

Support the timeline of operations and TTL (Time-To-Live) settings for records and fields. Each operation from previous levels now has an alternative version with a timestamp parameter to represent when the operation was executed. For each field-value pair in the database, the TTL determines how long that value will persist before being removed.

Notes:

  • Time should always flow forward, so timestamps are guaranteed to strictly increase as operations are executed.
  • Each test cannot contain both versions of operations (with and without timestamp). However, you should maintain backward compatibility, so all previously defined methods should work in the same way as before.
  • set_at(self, key: str, field: str, value: str, timestamp: int) -> None β€” should insert a field-value pair or updates the value of the field in the record associated with key.
  • set_at_with_ttl(self, key: str, field: str, value: str, timestamp: int, ttl: int) -> None β€” should insert a field-value pair or update the value of the field in the record associated with key. Also sets its Time-To-Live starting at timestamp to be ttl. The ttl is the amount of time that this field-value pair should exist in the database, meaning it will be available during this interval: [timestamp, timestamp + ttl).
  • delete_at(self, key: str, field: str, timestamp: int) -> bool β€” the same as delete, but with timestamp of the operation specified. Should return True if the field existed and was successfully deleted and False if the key didn’t exist.
  • get_at(self, key: str, field: str, timestamp: int) -> str | None β€” the same as get, but with timestamp of the operation specified.
  • scan_at(self, key: str, timestamp: int) -> list[str] β€” the same as scan, but with timestamp of the operation specified.
  • scan_by_prefix_at(self, key: str, prefix: str, timestamp: int) -> list[str] β€” the same as scan_by_prefix, but with timestamp of the operation specified.

Examples

The examples below show how these operations should work (the section is scrollable to the right):

Level 4

The database should be backed up from time to time. Introduce operations to support backing up and restoring the database state based on timestamps. When restoring, ttl expiration times should be recalculated accordingly.

  • backup(self, timestamp: int) -> int β€” should save the database state at the specified timestamp, including the remaining ttl for all records and fields. Remaining ttl is the difference between their initial ttl and their current lifespan (the duration between the timestamp of this operation and their initial timestamp). Returns the number of non-empty non-expired records in the database.
  • restore(self, timestamp: int, timestamp_to_restore: int) -> None β€” should restore the database from the latest backup before or at timestamp_to_restore. It’s guaranteed that a backup before or at timestamp_to_restore will exist. Expiration times for restored records and fields should be recalculated according to the timestamp of this operation β€” since the database timeline always flows forward, restored records and fields should expire after the timestamp of this operation, depending on their remaining ttl at backup.

Examples

The examples below show how these operations should work (the section is scrollable to the right):

Execution time limit: 3 seconds

Memory limit: 4g

This Coinbase OA problem builds an in-memory database in four stages: basic set/get/delete by key and field, sorted scan operations, time-aware updates with TTL expiration, and finally backup/restore with recalculated lifetimes. A robust solution typically uses hash maps for records and fields, tracks expiration timestamps for each value, and filters out expired entries before answering queries. For the backup and restore layer, the important part is preserving enough state in snapshots to rebuild the database consistently at a later timestamp.

END
 0