Implement BFS serializer and deserializer
Company: Airtable
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This question evaluates the skill of serializing and deserializing Python object graphs using BFS, including handling primitives, dictionaries, shared subobjects, and circular references while preserving object identity.
Constraints
- The graph contains only int, str, and dict objects; dictionary keys are strings.
- The graph may contain cycles and shared dictionary subobjects.
- The number of reachable nodes is at most 10000.
- The total number of dictionary fields is at most 50000.
- For deterministic output, assign IDs '0', '1', ... by BFS first discovery and visit dictionary keys in lexicographic order.
- Every child identifier in an input representation exists in value_map.
Examples
Input: ('serialize', 42)
Expected Output: {'root_id': '0', 'value_map': {'0': 42}}
Explanation: A primitive root receives ID '0' and is stored directly as its raw value.
Input: ('serialize', {})
Expected Output: {'root_id': '0', 'value_map': {'0': {}}}
Explanation: An empty dictionary has no child fields, so its stored mapping is empty.
Input: ('serialize', {'b': 'hi', 'a': {'x': 1}})
Expected Output: {'root_id': '0', 'value_map': {'0': {'a': '1', 'b': '2'}, '1': {'x': '3'}, '2': 'hi', '3': 1}}
Explanation: The root is ID '0'. Keys are visited as 'a', then 'b', so the nested dictionary is ID '1' and 'hi' is ID '2'. The nested value 1 is then discovered as ID '3'.
Input: ('deserialize', {'root_id': 'r', 'value_map': {'r': {'right': 'child', 'left': 'child'}, 'child': {'value': 'v'}, 'v': 7}})
Expected Output: {'root_id': '0', 'value_map': {'0': {'left': '1', 'right': '1'}, '1': {'value': '2'}, '2': 7}}
Explanation: Both 'left' and 'right' point to the same reconstructed dictionary, so canonical serialization reuses child ID '1' for both fields.
Input: ('deserialize', {'root_id': 'root', 'value_map': {'root': {'self': 'root', 'n': 'one'}, 'one': 1}})
Expected Output: {'root_id': '0', 'value_map': {'0': {'n': '1', 'self': '0'}, '1': 1}}
Explanation: The 'self' field points back to the root dictionary, so it is serialized as ID '0' rather than duplicating the node.
Input: ('deserialize', {'root_id': 'A', 'value_map': {'A': {'next': 'B', 'name': 'N1'}, 'B': {'next': 'A', 'name': 'N2'}, 'N1': 'a', 'N2': 'b'}})
Expected Output: {'root_id': '0', 'value_map': {'0': {'name': '1', 'next': '2'}, '1': 'a', '2': {'name': '3', 'next': '0'}, '3': 'b'}}
Explanation: This representation encodes a two-node cycle. The canonical BFS serialization preserves B's back-reference to A as 'next': '0'.
Hints
- Use a queue for BFS and a map from object identity to assigned string ID. Enqueue an object only when it is first discovered.
- For deserialization, allocate all dictionary nodes first, then fill their fields in a second pass so cycles and back-references can be wired correctly.