Reference-Counted Deduplicating File System
Reported by candidates from Citadel's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.
Citadel reported this one in September 2026, and it looks like a plain file system design question until the overwrite rule bites. You get WRITE, READ and DELETE on paths, with identical contents sharing one physical block through reference counts. The pattern is design with hash tables, and the trap is order of operations when a WRITE hits an existing path. If you're taking this OA in the next couple of days, read the overwrite rule twice. StealthCoder sits invisibly on your screen as a safety net if you blank mid-assessment, but the logic below is short enough to carry in your head.
The problem
Implement an in-memory file system that processes an ordered sequence of operations. Logical paths may share one physical copy when their file contents are identical. The file system starts empty. Each row of operations has one of these forms: ["WRITE", path, data]: If path already exists, release its current reference first and immediately remove a physical block whose reference count becomes zero. Then store data at path. Search only physical blocks with the same data length. When that size bucket is nonempty, use the mock hash equal to the sum of the data's ASCII character codes to narrow the candidates, and confirm a match with an exact content comparison. Reuse a matching block or create a new physical block. ["READ", path]: Read the content referenced by path. ["DELETE", path]: Remove path. Decrement its block's reference count and release the physical block when the count reaches zero. Return one row for each operation, in order: A WRITE returns ["true"] when it creates a new physical copy and ["false"] when it reuses an existing copy. A successful READ returns [data]. A missing read is the portable representation of null and returns an empty row []. A DELETE returns ["true"] when it removes an existing logical path and ["false"] when the path is missing. Physical blocks are independent of the path that first introduced them. Deleting that path must not affect other logical paths that still reference the same block. Hash collisions do not imply equal content. Function processFileOperations(operations: String[][]) → String[][] Examples Example 1 operations = [["WRITE","/a","alpha"],["WRITE","/b","alpha"],["READ","/b"],["DELETE","/a"],["READ","/b"],["DELETE","/b"],["READ","/b"]] return = [["true"],["false"],["alpha"],["true"],["alpha"],["true"],[]] The first write allocates a block. The second path shares it. Deleting /a leaves the shared content readable through /b; deleting the final reference releases the block, so the last read returns an empty row. Example 2 operations = [["WRITE","/left","ab"],["WRITE","/right","ba"],["WRITE","/copy","ab"],["DELETE","/left"],["READ","/copy"],["READ","/right"]] return = [["true"],["true"],["false"],["true"],["ab"],["ba"]] ab and ba have the same length and mock hash, but their exact contents differ, so each needs a physical block. The third write shares the ab block, which remains available after /left is deleted. Example 3 operations = [["WRITE","/x","red"],["WRITE","/y","red"],["WRITE","/x","blue"],["DELETE","/missing"],["READ","/x"],["READ","/y"],["WRITE","/y","blue"],["DELETE","/x"],["READ","/y"]] return = [["true"],["false"],["true"],["false"],["blue"],["red"],["false"],["true"],["blue"]] Overwriting /x releases only its reference to red, which remains alive through /y, and creates a new blue block. Overwriting /y later releases the final red reference and shares the existing blue block. Constraints 1 <= operations.length <= 100000. Every operation has exactly one of the documented forms, and command names are uppercase. Each path is a non-empty printable ASCII string of length at most 128. Each data value is a printable ASCII string of length at most 100000; an empty string is allowed. The total length of all data values in WRITE operations is at most 1000000. Paths and file contents are compared case-sensitively.
Reported by candidates. Source: FastPrep
Pattern and pitfall
Keep three structures: a map from path to block id, a map from block id to its data and reference count, and a bucket map keyed by data length, then by mock hash (sum of ASCII codes), holding block ids. On WRITE, if the path exists, decrement its block first. If the count hits zero, delete the block from its bucket right away. Only then look for a match, because the old block must not be reused after release. Matching means same length, same hash, then exact string equality. Example 2 shows why: ab and ba collide on hash but differ. The pitfall is rewriting identical data to the same path. Release first, then the block is gone and gets recreated, so return true. Follow the spec literally. Missing READ returns an empty row, missing DELETE returns false. StealthCoder is your hedge if the live OA rattles you on the release-before-match ordering.
The honest play: practice the pattern, and have StealthCoder ready for the one you didn't see coming.
You can drill Reference-Counted Deduplicating File System 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. Built for the candidate who saw this exact problem leak two days before his OA and wondered if anyone had a play.
Get StealthCoderRelated leaked OAs
You've seen the question.
Make sure you actually pass Citadel's OA.
Citadel reuses patterns across OAs. Built for the candidate who saw this exact problem leak two days before his OA and wondered if anyone had a play. Works on HackerRank, CodeSignal, CoderPad, and Karat.
Reference-Counted Deduplicating File System FAQ
What's the trick in the Citadel deduplicating file system problem?+
Reference counting with strict ordering. On an overwrite you release the old reference first and free the block immediately if the count hits zero. Only then search for a match. Get that order wrong and you'll reuse a block that should already be gone, flipping true and false outputs.
Why does the problem mention hash collisions?+
The mock hash is the sum of ASCII codes, so ab and ba collide. A hash match only narrows candidates. You must confirm with an exact string comparison before sharing a block. Example 2 tests exactly this, and skipping the comparison fails it.
What happens if I overwrite a path with the same content it already has?+
The spec says release the current reference first. If that path was the only reference, the block is freed and the new write creates a fresh block, returning true. If another path still holds it, the block survives and you reuse it, returning false.
How hard is this really?+
Algorithmically easy, no graph or DP. It's a careful design and state-tracking problem. Difficulty comes from bookkeeping across three maps and the edge cases. Constraints allow 100000 operations and 1000000 total data length, so avoid rescanning every block per write.
How do I prepare for this in 48 hours?+
Code it once from scratch using the three examples as tests. Trace Example 3 by hand, since it covers overwrites with shared blocks. Practice similar design problems with reference counts or LRU-style maps. Watch for missing path handling on READ and DELETE, which return an empty row and false.