Implement an In-Memory File System
Company: eBay
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Take-home Project
Quick Answer: This question evaluates data structures and algorithmic skills alongside system-design thinking for managing stateful in-memory storage, ownership and capacity accounting, file operations, copying, and compression.
Part 1: Implement an In-Memory File System
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size <= 10^9
- File names are strings of length 1 to 50
- File names are unique within the file system
Examples
Input: [("addFile", "a", 3), ("addFile", "b", 5), ("addFile", "c", 5), ("listFiles",), ("getFileSize", "a"), ("deleteFile", "b"), ("listFiles",)]
Expected Output: [True, True, True, ['b(5)', 'c(5)', 'a(3)'], 3, True, ['c(5)', 'a(3)']]
Explanation: Files are listed by size descending. For the tie between b and c, name ascending puts b first.
Input: [("addFile", "readme", 1), ("addFile", "readme", 2), ("getFileSize", "missing"), ("deleteFile", "missing")]
Expected Output: [True, False, None, False]
Explanation: Duplicate adds fail, and operations on missing files return None or False as required.
Input: [("addFile", "beta", 4), ("addFile", "alpha", 4), ("listFiles",)]
Expected Output: [True, True, ['alpha(4)', 'beta(4)']]
Explanation: When sizes tie, sort by name ascending.
Input: [("listFiles",)]
Expected Output: [[]]
Explanation: Edge case: listing an empty file system returns an empty list.
Hints
- A hash map from file name to size gives O(1) average-time add, lookup, and delete.
- For listFiles, sort items with a key like (-size, name).
Part 2: Progressive File System - Level 1: Basic Operations
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size <= 10^9
- File names are strings of length 1 to 50
- No duplicate file names are allowed
Examples
Input: [("addFile", "a", 10), ("getFileSize", "a"), ("deleteFile", "a"), ("getFileSize", "a")]
Expected Output: [True, 10, True, None]
Explanation: A file can be added, queried, deleted, and then no longer found.
Input: [("addFile", "x", 1), ("addFile", "x", 2), ("deleteFile", "y")]
Expected Output: [True, False, False]
Explanation: Adding a duplicate file fails, and deleting a missing file also fails.
Input: []
Expected Output: []
Explanation: Edge case: no operations means no results.
Input: [("addFile", "solo", 7), ("deleteFile", "solo"), ("deleteFile", "solo")]
Expected Output: [True, True, False]
Explanation: Deleting the same file twice only succeeds the first time.
Hints
- You only need a dictionary from file name to size.
- Be careful about the difference between returning False and returning None.
Part 3: Progressive File System - Level 2: Search / Filtering
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size <= 10^9
- File names, prefixes, and suffixes have length between 0 and 50
- An empty prefix or suffix is allowed
Examples
Input: [("addFile", "apple.log", 4), ("addFile", "app_error.log", 10), ("addFile", "app.log", 10), ("findFiles", "app", ".log")]
Expected Output: [True, True, True, ['app.log(10)', 'app_error.log(10)', 'apple.log(4)']]
Explanation: All three names start with 'app' and end with '.log'. The two size-10 files are ordered by name.
Input: [("addFile", "a.txt", 2), ("addFile", "b.log", 3), ("addFile", "notes.txt", 5), ("findFiles", "", ".txt")]
Expected Output: [True, True, True, ['notes.txt(5)', 'a.txt(2)']]
Explanation: An empty prefix matches every name, so only the suffix filter matters here.
Input: [("findFiles", "pre", "suf")]
Expected Output: [[]]
Explanation: Edge case: searching an empty system returns no matches.
Input: [("addFile", "ab", 3), ("addFile", "ac", 3), ("addFile", "ab", 4), ("findFiles", "a", "")]
Expected Output: [True, True, False, ['ab(3)', 'ac(3)']]
Explanation: Duplicate adds fail. An empty suffix matches every ending.
Hints
- Python's startswith and endswith already handle empty strings the way you want.
- Collect matching files first, then sort them with the same ordering rule as listFiles.
Part 4: Progressive File System - Level 3: User and Capacity
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size, capacity <= 10^9
- User IDs and file names are strings of length 1 to 50
- File names are unique globally
Examples
Input: [("addUser", "u1", 10), ("addFileByUser", "u1", "a", 4), ("addFileByUser", "u1", "b", 6), ("addFileByUser", "u1", "c", 1), ("getFileSize", "b")]
Expected Output: [True, True, True, False, 6]
Explanation: The third file would exceed u1's capacity of 10, so it is rejected.
Input: [("addUser", "u1", 5), ("addUser", "u2", 5), ("addFileByUser", "u3", "x", 1), ("addFileByUser", "u1", "a", 3), ("addFileByUser", "u2", "a", 1), ("getFileSize", "a")]
Expected Output: [True, True, False, True, False, 3]
Explanation: Adding for a missing user fails, and file names must stay globally unique.
Input: [("addUser", "u0", 0), ("addFileByUser", "u0", "empty", 0), ("getFileSize", "missing")]
Expected Output: [True, True, None]
Explanation: Edge case: a zero-capacity user can still store a zero-size file.
Input: [("addUser", "u1", 7), ("addUser", "u1", 9)]
Expected Output: [True, False]
Explanation: Duplicate users are not allowed.
Hints
- Store each user's capacity and currently used space.
- A second map from file name to its size and owner makes getFileSize easy.
Part 5: Progressive File System - Level 4: Capacity Update
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size, capacity, newCapacity <= 10^9
- User IDs and file names are strings of length 1 to 50
- File names are unique globally
Examples
Input: [("addUser", "u1", 15), ("addFileByUser", "u1", "a", 4), ("addFileByUser", "u1", "b", 6), ("addFileByUser", "u1", "c", 3), ("updateCapacity", "u1", 7), ("listFiles",)]
Expected Output: [True, True, True, True, 1, ['a(4)', 'c(3)']]
Explanation: Shrinking from total size 13 to capacity 7 removes the largest file b(6).
Input: [("addUser", "u1", 20), ("addUser", "u2", 10), ("addFileByUser", "u1", "aa", 5), ("addFileByUser", "u1", "ab", 5), ("addFileByUser", "u1", "c", 4), ("addFileByUser", "u2", "x", 7), ("updateCapacity", "u1", 4), ("listFiles",)]
Expected Output: [True, True, True, True, True, True, 2, ['x(7)', 'c(4)']]
Explanation: u1 must drop two size-5 files. The tie is broken by name, so aa is removed before ab.
Input: [("updateCapacity", "ghost", 5)]
Expected Output: [None]
Explanation: Edge case: updating a missing user returns None.
Input: [("addUser", "u1", 8), ("addFileByUser", "u1", "a", 3), ("addFileByUser", "u1", "b", 5), ("updateCapacity", "u1", 8), ("listFiles",)]
Expected Output: [True, True, True, 0, ['b(5)', 'a(3)']]
Explanation: If the user already fits in the new capacity, no files are removed.
Hints
- Track each user's used space and the set of file names they own.
- When shrinking capacity, you only need to sort that one user's files by the required removal order.
Part 6: File Copy and Ownership
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size, capacity <= 10^9
- User IDs and file names are strings of length 1 to 50
- File names are unique globally
Examples
Input: [("addUser", "u1", 10), ("addFileByUser", "u1", "report", 4), ("copyFile", "report", "backup"), ("getFileOwner", "backup"), ("getFileSize", "backup")]
Expected Output: [True, True, True, 'u1', 4]
Explanation: The copy succeeds, keeps the same owner, and has the same size as the original.
Input: [("addUser", "u1", 15), ("addFileByUser", "u1", "a", 4), ("addFileByUser", "u1", "b", 1), ("copyFile", "a", "b")]
Expected Output: [True, True, True, False]
Explanation: Copying fails because the destination name already exists.
Input: [("addUser", "u1", 5), ("copyFile", "missing", "x"), ("getFileOwner", "x")]
Expected Output: [True, False, None]
Explanation: Edge case: copying a non-existent source fails.
Input: [("addUser", "u1", 7), ("addFileByUser", "u1", "a", 4), ("copyFile", "a", "b"), ("getFileSize", "b")]
Expected Output: [True, True, False, None]
Explanation: The copy would raise u1's used storage from 4 to 8, exceeding capacity 7.
Hints
- Store both size and owner for every file.
- When copying, check destination existence, source existence, and the owner's remaining capacity in that order.
Part 7: Compression Feature
Constraints
- 0 <= len(operations) <= 10^4
- 0 <= size <= 10^9
- File names are strings of length 1 to 50
- File names are unique
Examples
Input: [("addFile", "doc", 9), ("compressFile", "doc"), ("getFileSize", "doc"), ("decompressFile", "doc"), ("getFileSize", "doc")]
Expected Output: [True, True, 4, True, 9]
Explanation: Compression uses integer division, and decompression restores the original size exactly.
Input: [("addFile", "x", 8), ("compressFile", "x"), ("compressFile", "x"), ("getFileSize", "x")]
Expected Output: [True, True, False, 4]
Explanation: A file cannot be compressed twice without an intervening decompression.
Input: [("addFile", "y", 5), ("decompressFile", "y"), ("getFileSize", "y")]
Expected Output: [True, False, 5]
Explanation: Edge case: decompressing an uncompressed file fails.
Input: [("addFile", "tiny", 1), ("compressFile", "tiny"), ("getFileSize", "tiny"), ("decompressFile", "tiny"), ("getFileSize", "tiny"), ("compressFile", "missing")]
Expected Output: [True, True, 0, True, 1, False]
Explanation: A size-1 file compresses to 0, and a missing file cannot be compressed.
Hints
- For each file, store both the current size and the original size.
- A boolean compressed flag is enough to prevent invalid double-compress or invalid decompress.