PracHub
QuestionsCoachesLearningGuidesInterview Prep

Quick Overview

This question evaluates understanding of persistent/versioned data structures, iterator semantics, concurrency control, and complexity-aware design for mutable sets, and is categorized under Coding & Algorithms.

  • medium
  • Databricks
  • Coding & Algorithms
  • Software Engineer

Implement a snapshotable set with iterators

Company: Databricks

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Onsite

Implement a SnapshotSet data structure with the following API: add(x), remove(x), contains(x), snapshot() -> sid, and iterate(sid) -> iterator over the elements as of snapshot sid. Support many concurrent snapshots while allowing the live set to change. Optimize for O(log n) updates and O( 1) iterator next on average. Discuss design choices (copy-on-write, persistent balanced tree, or versioned hash buckets), memory usage, and snapshot reclamation. Provide unit tests for empty set, deletions before/after snapshots, and concurrent mutation during iteration.

Quick Answer: This question evaluates understanding of persistent/versioned data structures, iterator semantics, concurrency control, and complexity-aware design for mutable sets, and is categorized under Coding & Algorithms.

Implement a **snapshotable integer set**: a mutable set of integers from which you can take read-only snapshots, then iterate over any snapshot's contents in sorted order — even while the live set keeps changing afterward. ## What to implement Write a function `solution(operations)` that replays a sequence of operations against a single live set (initially empty) and returns a list of results. `operations` is a **list of tuples**. The first element of each tuple is the command name (a string); the remaining element, when present, is the argument. ## Operations Some operations mutate the live set and produce **no output**: - `('add', x)` — insert integer `x` into the live set. If `x` is already present, do nothing. - `('remove', x)` — remove `x` from the live set if it is present. If `x` is absent, do nothing. The remaining operations each **append one value** to the result list: - `('contains', x)` — return `True` if `x` is in the **current live set**, else `False`. This always checks the live set, never a snapshot. - `('snapshot',)` — record the current contents of the live set and return the new **snapshot id** `sid`. Snapshot ids are assigned `0, 1, 2, …` in the order snapshots are taken. A snapshot may be taken of an empty live set. - `('iterator', sid)` — create a new in-order iterator over the elements that existed in snapshot `sid`, and return the new **iterator id** `iid`. Iterator ids are assigned `0, 1, 2, …` in the order iterators are created. - `('next', iid)` — advance iterator `iid` and return its next element in **strictly increasing order**, or `None` if the iterator has already yielded every element of its snapshot. ## Snapshot and iterator semantics - A **snapshot** captures the live set exactly as it was at the moment `('snapshot',)` ran. Later `add`/`remove` operations on the live set do **not** change any existing snapshot. - An **iterator** is bound to its snapshot and is fully **isolated from later mutations**: once created, it yields exactly the elements of that snapshot in increasing order, regardless of any `add`/`remove` calls (or new snapshots) that happen afterward. - Each iterator tracks its own position. Calling `('next', iid)` repeatedly walks that snapshot's elements from smallest to largest; after the last element, every further `next` on that iterator returns `None`. - Multiple iterators (over the same or different snapshots) can be active at once, each advancing independently. ## Output Return a list containing the result of every operation that produces output, in the order those operations appear: - `contains` → `bool` - `snapshot` → `int` (the snapshot id) - `iterator` → `int` (the iterator id) - `next` → `int`, or `None` when the iterator is exhausted ## Example Given `operations`: ``` ('add', 1), ('add', 2), ('snapshot',), ('remove', 1), ('snapshot',), ('iterator', 0), ('next', 0), ('next', 0), ('next', 0), ('iterator', 1), ('next', 1), ('next', 1), ('contains', 1) ``` the result is: ``` [0, 1, 0, 1, 2, None, 1, 2, None, False] ``` - After the two adds, snapshot 0 captures `{1, 2}` (returns `0`). - `remove(1)` leaves the live set as `{2}`; snapshot 1 captures `{2}` (returns `1`). - Iterator 0 (over snapshot 0) yields `1`, then `2`, then `None`. - Iterator 1 (over snapshot 1) yields `2`, then `None`. - `contains(1)` is `False` because `1` was removed from the live set. ## Constraints - `1 <= len(operations) <= 200000` - Values are integers in the range `[-10^9, 10^9]`. - Every snapshot id and iterator id used as an argument is valid (it refers to a snapshot/iterator that was already created). - **Target complexity:** `add` / `remove` / `contains` in `O(log n)` expected, `snapshot` in `O(1)`, iterator creation in `O(log n)`, and `next` in `O(1)` amortized. > **Interview discussion note:** a full design discussion would compare copy-on-write (simple but expensive updates), versioned hash buckets (good lookups but awkward ordered iteration), and persistent balanced trees. For this coding task, aim for the persistent balanced-tree style solution. Snapshot reclamation can be handled by dropping unused snapshot roots/iterators so unreachable nodes are garbage-collected.

Constraints

  • 1 <= len(operations) <= 200000
  • Values are integers in the range [-10^9, 10^9]
  • Snapshot ids and iterator ids used in the input are valid
  • Target complexity: add/remove/contains in O(log n) expected, snapshot in O(1), iterator creation in O(log n), and next in O(1) amortized

Examples

Input: ([('snapshot',), ('iterator', 0), ('next', 0), ('contains', 5)],)

Expected Output: [0, 0, None, False]

Explanation: Snapshot 0 is an empty set. Its iterator is immediately exhausted, and 5 is not in the live set.

Input: ([('add', 1), ('add', 2), ('snapshot',), ('remove', 1), ('snapshot',), ('iterator', 0), ('next', 0), ('next', 0), ('next', 0), ('iterator', 1), ('next', 1), ('next', 1), ('contains', 1)],)

Expected Output: [0, 1, 0, 1, 2, None, 1, 2, None, False]

Explanation: Snapshot 0 contains {1,2}. After removing 1, snapshot 1 contains {2}. The old snapshot still iterates 1,2, while the live set no longer contains 1.

Input: ([('add', 3), ('add', 1), ('snapshot',), ('iterator', 0), ('next', 0), ('add', 2), ('remove', 3), ('next', 0), ('next', 0), ('snapshot',), ('iterator', 1), ('next', 1), ('next', 1), ('next', 1)],)

Expected Output: [0, 0, 1, 3, None, 1, 1, 1, 2, None]

Explanation: Iterator 0 is created from snapshot 0 = {1,3}. Even after the live set changes to {1,2}, iterator 0 still yields 3. Snapshot 1 reflects the later live set.

Input: ([('add', -1), ('add', -1), ('remove', 5), ('contains', -1), ('snapshot',), ('remove', -1), ('contains', -1), ('iterator', 0), ('next', 0), ('next', 0)],)

Expected Output: [True, 0, False, 0, -1, None]

Explanation: Duplicate add has no effect, removing a missing value has no effect, and the snapshot preserves -1 even after it is removed from the live set.

Hints

  1. A snapshot can be just a pointer/reference to an immutable version of the set instead of a full copy.
  2. If the set is stored in a persistent balanced BST, an iterator can keep a stack of the current in-order path and get amortized O(1) next() calls.
Last updated: Apr 23, 2026

Loading coding console...

PracHub

Master your tech interviews with 8,500+ real questions from top companies.

Product

  • Questions
  • Learning Tracks
  • Interview Guides
  • Resources
  • Premium
  • For Universities
  • Student Access

Browse

  • By Company
  • By Role
  • By Category
  • Topic Hubs
  • SQL Questions
  • AI Coding Questions
  • Compare Platforms
  • Discord Community

Support

  • support@prachub.com
  • (916) 541-4762

Legal

  • Privacy Policy
  • Terms of Service
  • About Us

© 2026 PracHub. All rights reserved.

Related Coding Questions

  • Design an n x n Tic-Tac-Toe Game - Databricks (medium)
  • IPv4 CIDR Range Membership Queries - Databricks (medium)
  • Optimal Commute: Nearest Transit Distance in a City Grid - Databricks (medium)
  • Choose the Best Travel Mode - Databricks (medium)
  • Implement an Alternating Tic-Tac-Toe Game - Databricks (hard)