Vault File System
Role: Software Engineer
Build an in-memory file directory system that models Harvey's vault — a hierarchical structure of folders and files with capacity limits, deduplication, and file comparison.
Background
At Harvey, users upload files to a vault which is organized like a file directory. Folders can contain files or sub-folders. You'll implement this as an in-memory system across three parts.
Constraints:
- Each folder is limited to 5 files/sub-folders. Adding beyond this limit returns
None(orFalse) - File paths look like
path/to/file.txt; folder paths look likepath/to/folder - All names are alphanumeric — you can assume valid, clean input
Part 1 — Core File System
Implement a Vault class with the following interface:
class Vault:
def add_file(self, path: str) -> bool:
"""Add a file at the given path. Recursively creates folders as needed.
Returns False if any folder along the path is at capacity (5 items)."""
...
def get_files(self, folder_path: str) -> list[str] | None:
"""Return the names of all direct children of the given folder.
Not recursive. Returns None if the folder doesn't exist."""
...Example:
vault = Vault()
vault.add_file("docs/legal/contract.txt") # True — creates docs/, docs/legal/
vault.add_file("docs/legal/nda.txt") # True
vault.get_files("docs/legal") # ["contract.txt", "nda.txt"]
vault.get_files("docs") # ["legal"]
vault.get_files("nonexistent") # NoneApproach — Trie / nested dict:
Model the file system as a tree. Each node is a folder with a dict of children (sub-folders or files). Files are leaf nodes.
from __future__ import annotations
from dataclasses import dataclass, field
MAX_CHILDREN = 5
@dataclass
class Node:
name: str
is_file: bool = False
children: dict[str, Node] = field(default_factory=dict)
def at_capacity(self) -> bool:
return len(self.children) >= MAX_CHILDREN
class Vault:
def __init__(self):
self._root = Node(name="")
def _get_node(self, parts: list[str]) -> Node | None:
current = self._root
for part in parts:
if part not in current.children:
return None
current = current.children[part]
return current
def add_file(self, path: str) -> bool:
parts = path.split("/")
current = self._root
for part in parts[:-1]: # traverse/create intermediate folders
if part not in current.children:
if current.at_capacity():
return False
current.children[part] = Node(name=part)
current = current.children[part]
filename = parts[-1]
if filename in current.children:
return True # already exists
if current.at_capacity():
return False
current.children[filename] = Node(name=filename, is_file=True)
return True
def get_files(self, folder_path: str) -> list[str] | None:
parts = folder_path.split("/")
node = self._get_node(parts)
if node is None or node.is_file:
return None
return list(node.children.keys())Part 2 — Deduplication (add_files)
Extend the vault with a bulk-add method that skips duplicate files:
def add_files(self, paths: list[str]) -> dict[str, bool]:
"""Add multiple files. Skip duplicates (same path already exists).
Returns a dict mapping each path to True (added) or False (skipped/failed)."""
...What counts as a duplicate? A file at the same path that is already in the vault.
vault = Vault()
vault.add_files(["docs/a.txt", "docs/b.txt", "docs/a.txt"])
# → {"docs/a.txt": True, "docs/b.txt": True, "docs/a.txt": False}Implementation:
def add_files(self, paths: list[str]) -> dict[str, bool]:
results = {}
seen_in_batch = set()
for path in paths:
if path in seen_in_batch:
results[path] = False
continue
added = self.add_file(path)
results[path] = added
if added:
seen_in_batch.add(path)
return resultsNote: The vault already handles the case where add_file is called on an existing path (returns True without re-adding). The seen_in_batch set is only needed to catch duplicates within the same add_files call if you want accurate per-call reporting.
Part 3 — Verifying File Equality
"How do you verify whether two files — their metadata and content — are the same?"
This is a design/discussion question. Two main approaches:
Option A — Iterative File I/O
Read both files chunk by chunk and compare byte-by-byte. Also compare metadata (size, name, last modified).
import os
def files_are_equal(path_a: str, path_b: str) -> bool:
stat_a, stat_b = os.stat(path_a), os.stat(path_b)
if stat_a.st_size != stat_b.st_size:
return False # fast path: sizes differ
with open(path_a, "rb") as fa, open(path_b, "rb") as fb:
chunk_size = 8192
while True:
chunk_a = fa.read(chunk_size)
chunk_b = fb.read(chunk_size)
if chunk_a != chunk_b:
return False
if not chunk_a: # both exhausted simultaneously
return TruePros: Exact comparison, no false positives, works on any file size Cons: O(n) reads for every comparison — slow if comparing many files
Option B — Hashing
Compute a cryptographic hash (e.g., SHA-256) of each file's contents. Two files with identical hashes are considered equal.
import hashlib
def file_hash(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def files_are_equal(path_a: str, path_b: str) -> bool:
return file_hash(path_a) == file_hash(path_b)Pros: Hash can be precomputed and cached — comparing N files is O(N) hashes, then O(1) comparisons Cons: Tiny probability of hash collision (negligible with SHA-256); requires reading the full file once to hash
Option C — Python's filecmp
Python's built-in filecmp.cmp(f1, f2, shallow=False) compares both metadata and content. Simplest for quick use.
import filecmp
filecmp.cmp("file_a.txt", "file_b.txt", shallow=False) # True if identicalTrade-off Summary
| Approach | Speed (one-time) | Speed (repeated) | False positives |
|---|---|---|---|
| Byte-by-byte | O(n) | O(n) each time | None |
| Hashing | O(n) to hash | O(1) after caching | Negligible |
filecmp | O(n) | O(n) | None |
In a vault deduplication context: Hashing is ideal — compute and store the hash when a file is uploaded, then compare hashes on subsequent uploads in O(1).
Follow-ups
- How would you handle the capacity limit at the root level?
- How would you support
move_file(src, dst)— what edge cases arise? - If the vault were persisted to disk, how would your deduplication strategy change?
- How would you make the file system thread-safe for concurrent uploads?
- What is the time complexity of
add_filefor a path of depthd?
Variant (independent report)
A second report confirms the same three-part question in a live, screen-shared coding round. The Part 1 prompt below is transcribed verbatim from the on-screen instructions shown to the candidate:
Instructions Part 1
Please share your full screen with your interviewer.
At Harvey, users can upload files to a vault, which is equivalent to a file directory. In the vault, users can setup folders to organize their files. You'll be building an in-memory vault directory system.
Part 1: Setup a file system that can support the following methods:
add_file(path): Add a file given a file path. Recursively creates folders.get_files(path) -> str[]: Return the name of the folder's files given a folder path. Not recursive.- Constraint: each folder is limited to 5 files/sub-folders. If the limit is reached, return None or False for any subsequent additions.
An example of a file path is
path/to/file.txt. An example of a folder path ispath/to/folder.You can also assume you are given valid paths (e.g. no hidden characters), file names are alphanumerics.
Follow-up parts as stated by the interviewer:
Part 2: Deduplicate (add_files) Part 3: explain how you verify whether the two files, meta data/file data is the same
Source: community report, March 2026