Reported May 2026
Rampdesign

Cloud Storage System, Part 4: Backup and Restore

Reported by candidates from Ramp's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Ramp OA. Under 2s to a working solution.
Founder's read

The mistake that sinks a first attempt on this Ramp question is treating backup as a shallow copy. Ramp reported this one in May 2026, and it's Part 4 of a four-level in-memory cloud storage design problem. Levels 1 to 3 cover files, prefix top-N and users with capacity. Level 4 adds backup and restore of a user's files on top of all that state. You return one string row per operation, so the adapter format matters too. If you blank on the state bookkeeping mid-assessment, StealthCoder is the invisible safety net that reads the screen and gives you a working structure.

The problem

Your task is to implement a simple cloud storage system. All operations that should be supported are listed below.
Solving this task consists of several levels. Subsequent levels are opened when the current level is correctly solved. You always have access to the data for the current and all previous levels.
Requirements
Your task is to implement a simple cloud storage system that maps objects (files) to their metainformation. Specifically, the storage should maintain files and information about them, including each file name and size. This system is in-memory; it does not use the real filesystem.
The levels are cumulative:
Level 1 supports adding, retrieving, and deleting files.
Level 2 displays the largest files matching a prefix.
Level 3 adds capacity-limited users and user merging.
Level 4 backs up and restores a user's files.
To move to the next level, all tests at the current level must pass.
Note
The queries never call operations that produce a collision between a file name and a directory name.
FastPrep Operation-Sequence Adapter
FastPrep calls the function once with operations. Process the rows from left to right while preserving one shared storage state. Every row starts with an uppercase operation name followed by that method's string arguments.
Return one string-array row for every input operation:
Encode a Boolean as ["true"] or ["false"].
Encode an integer as one element, such as ["10"].
Encode None as an empty row [].
Return the formatted list from GET_N_LARGEST directly; an empty list is also [].
Multipart Series
Part 1: File Operations
Part 2: Largest Files
Part 3: Users and Capacity
Part 4: Backup and Restore
Level 1: File Operations
The cloud storage system should support file manipulation.
add_file(self, name: str, size: int) -> bool adds a new file name to the storage. size is the amount of memory required in bytes. The operation fails if a file with the same name already exists. Return True if the file was added successfully or False otherwise. The adapter row is ["ADD_FILE", name, size]. Files added this way are owned by the unlimited admin user.
get_file_size(self, name: str) -> int | None returns the size of file name if it exists, or None otherwise. The adapter row is ["GET_FILE_SIZE", name].
delete_file(self, name: str) -> int | None deletes file name. Return the deleted file size when deletion succeeds, or None if the file does not exist. The adapter row is ["DELETE_FILE", name].
Level 2: Largest Files
Implement an operation for retrieving statistics about files with a specific prefix.
get_n_largest(self, prefix: str, n: int) -> list[str] returns the names of the top n largest files whose names start with prefix, formatted as ["<name_1>(<size_1>)",..., "<name_n>(<size_n>)"].
Sort matching files by size in descending order. Break a size tie by file name in lexicographical ascending order.
If there are no matching files, return an empty list. If fewer than n files match, return all of them in the specified format.
The adapter row is ["GET_N_LARGEST", prefix, n].
The visible source table contains one malformed no-match call whose second argument is a file name even though the declared signature requires n: int. The judged contract follows the declared signature: n is an integer, and a prefix with no matches returns an empty row.
Level 3: Users and Capacity
Support queries from different users. All users share one common filesystem, and every non-admin user has a storage-capacity limit.
add_user(self, user_id: str, capacity: int) -> bool adds a new user with capacity bytes. The total size of files owned by user_id cannot exceed this limit. The operation fails if the user already exists. Return True on success and False otherwise. The adapter row is ["ADD_USER", user_id, capacity].
add_file_by(self, user_id: str, name: str, size: int) -> int | None behaves like add_file, but the new file is owned by user_id. The operation fails when the user is absent, the name is already occupied, or the addition would exceed the user's capacity. Return the user's remaining capacity after success, or None otherwise. The adapter row is ["ADD_FILE_BY", user_id, name, size].
Every ADD_FILE operation from Level 1 is run by the admin user, who has unlimited storage capacity.
merge_user(self, user_id_1: str, user_id_2: str) -> int | None merges user_id_2 into user_id_1. Transfer ownership of every file owned by user_id_2, add the remaining storage capacity of user_id_2 to user_id_1's limit, and delete user_id_2. Return user_id_1's remaining capacity, or None when either user is absent or the IDs are equal. Neither merge argument is admin. The adapter row is ["MERGE_USER", user_id_1, user_id_2].
Level 4: Backup and Restore
Allow users to back up their files.
backup_user(self, user_id: str) -> int | None backs up the current state of every file owned by user_id, including each file name and size. The backup is stored separately and is not changed by later file-manipulation queries. A new backup replaces any previous backup for the same user. Return the number of backed-up files, or None if user_id does not exist. The adapter row is ["BACKUP_USER", user_id].
restore_user(self, user_id: str) -> int | None restores the user's files to the latest backup. If there is no backup, delete all files currently owned by the user. When a backed-up file name is currently occupied by another user, ignore that file. Return the number of files restored successfully, or None if user_id does not exist. The adapter row is ["RESTORE_USER", user_id].
merge_user does not change user_id_1's backup, and user_id_2 is deleted together with its backup.
restore_user does not change the user's capacity.

Function
cloudStorageSystem(operations: String[][]) → String[][]

Examples
Example 1
operations = [["ADD_FILE","/dir1/dir2/file.txt","10"],["ADD_FILE","/dir1/dir2/file.txt","5"],["GET_FILE_SIZE","/dir1/dir2/file.txt"],["DELETE_FILE","/non-existing.file"],["DELETE_FILE","/dir1/dir2/file.txt"],["GET_FILE_SIZE","/not-existing.file"]]
return = [["true"],["false"],["10"],[],["10"],[]]
This preserves every Level 1 row shown in the source example: the first add succeeds, the duplicate add fails, the existing size is 10, deleting a missing file returns None, deleting the existing file returns 10, and the final lookup returns None.
Example 2
operations = [["ADD_FILE","/dir/file1.txt","5"],["ADD_FILE","/dir/file2","20"],["ADD_FILE","/dir/deeper/file3.mov","9"],["GET_N_LARGEST","/dir","2"],["GET_N_LARGEST","/dir/file","3"],["GET_N_LARGEST","/another_dir","3"],["ADD_FILE","/big_file.mp4","20"],["GET_N_LARGEST","/","2"]]
return = [["true"],["true"],["true"],["/dir/file2(20)","/dir/deeper/file3.mov(9)"],["/dir/file2(20)","/dir/file1.txt(5)"],[],["true"],["/big_file.mp4(20)","/dir/file2(20)"]]
The first three files reproduce the source's Level 2 table. Prefix filtering keeps only matching names, size determines primary order, and the final root-prefix query resolves the size-20 tie lexicographically. The no-match row follows the explicit empty-list rule.
Example 3
operations = [["ADD_USER","user1","100"],["ADD_USER","user2","40"],["ADD_FILE_BY","user1","/a","60"],["ADD_FILE_BY","user2","/b","10"],["ADD_FILE_BY","user2","/c","30"],["MERGE_USER","user1","user2"],["GET_FILE_SIZE","/b"],["ADD_FILE_BY","user2","/d","1"],["ADD_FILE_BY","user1","/d","40"],["MERGE_USER","user1","user1"]]
return = [["true"],["true"],["40"],["30"],["0"],["40"],["10"],[],["0"],[]]
Before the merge, user1 has 40 bytes remaining and user2 has none. The merge transfers both files, deletes user2, and leaves user1 with 40 bytes. Calls for the deleted user and a self-merge return None.
Example 4
operations = [["ADD_USER","user","100"],["ADD_FILE_BY","user","/file3.mp4","60"],["ADD_FILE_BY","user","/file4.txt","10"],["BACKUP_USER","user"],["DELETE_FILE","/file3.mp4"],["DELETE_FILE","/file4.txt"],["ADD_FILE","/file3.mp4","140"],["ADD_FILE_BY","user","/dir/file5.new","20"],["RESTORE_USER","user"],["GET_FILE_SIZE","/file3.mp4"],["GET_FILE_SIZE","/file4.txt"],["GET_FILE_SIZE","/dir/file5.new"]]
return = [["true"],["40"],["30"],["2"],["60"],["10"],["true"],["80"],["1"],["140"],["10"],[]]
The backup remembers /file3.mp4 and /file4.txt. Before restore, admin occupies the former name and the user owns /dir/file5.new. Restore deletes the user's current file, skips the conflicting name, restores /file4.txt, and returns 1.

Constraints
1 <= operations.length <= 2000.
Every operation row is well-formed and uses one of the operation names described above.
Sizes, capacities, and n are positive base-10 integers at most 10^9.
File names, prefixes, and user IDs are non-empty case-sensitive ASCII strings of length at most 100.
The input does not create collisions between file and directory names.
All users share one file-name namespace; admin exists initially and has unlimited capacity.
Process operations in the supplied order. The system is empty initially except for admin.

Reported by candidates. Source: FastPrep

Pattern and pitfall

This is a design problem, so the trick is picking the right state early. Keep one global map from file name to size and owner, and one map from user to capacity and used bytes. Level 2 sorts matches by size descending, then name ascending. A linear scan with a sort is fine. Backup should snapshot that user's files by value, meaning copies of name and size, not references into live state. The classic pitfall is restoring without reconciling. Files that were deleted or overwritten since the backup, and files other users may now hold under the same name, need explicit rules. Restore must also respect capacity and keep used bytes in sync. Every earlier operation, including delete and merge, has to update used bytes, or Level 4 breaks quietly. Read the Level 4 text carefully, since it's cut off here. If you freeze on those edge rules live, StealthCoder is the hedge that works through them with you.

Drill it cold or hedge it with StealthCoder. Either way, don't walk into the OA hoping you remember the trick.

If this hits your live OA

You can drill Cloud Storage System, Part 4: Backup and Restore cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Made for the candidate who got the OA invite this morning and has 72 hours, not six months.

Get StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Ramp's OA.

Ramp reuses patterns across OAs. Made for the candidate who got the OA invite this morning and has 72 hours, not six months. Works on HackerRank, CodeSignal, CoderPad, and Karat.

Cloud Storage System, Part 4: Backup and Restore FAQ

How hard is the Ramp Cloud Storage System Part 4 really?+

Medium on algorithms, hard on bookkeeping. Nothing here needs a clever data structure. The difficulty is keeping file ownership, per-user used bytes and backups consistent across four levels. One missed update in Level 3 shows up as a failure in Level 4.

What's the trick for backup and restore?+

Snapshot by value. Copy the user's file names and sizes into a separate structure at backup time. Don't hold references to live objects. On restore, rebuild the user's files from that snapshot and recompute used bytes so capacity stays correct.

Why does the output come back as string rows?+

The adapter calls your function once with a list of operations and expects one row per operation. Booleans become ["true"] or ["false"], integers become one element, None becomes an empty row, and GET_N_LARGEST returns its list directly.

How should I order results in get_n_largest?+

Filter by prefix, then sort by size descending with file name ascending as the tiebreak. Format each as name(size). Return an empty list if nothing matches, and return everything if fewer than n match.

How do I prepare for this in 48 hours?+

Write a class with the Level 1 to 3 operations from memory and test used-byte accounting after every add and delete. Then sketch backup and restore as value snapshots. Practice the adapter's string encoding too, since formatting errors fail tests.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Ramp.

OA at Ramp?
Invisible during screen share
Get it