Last Updated: December 19, 2025
An In-Memory File System is a simplified version of a real file system (like NTFS, ext4, or APFS), designed to run entirely in memory.
It mimics the hierarchical structure of directories and files, enabling operations like:
In this chapter, we will explore the low-level design of file system in detail.
Let's start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions, clarify ambiguities, and define the system's scope more precisely.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: Should the file system support both files and directories in a hierarchical structure?
Interviewer: : Yes. The system should model a typical UNIX-style file system with nested files and directories.
Candidate: How will a user interact with this file system? Should I design a GUI, or a command-line interface?
Interviewer: A command-line interface (CLI) is the way to go. The system should be able to parse and execute a set of basic shell commands.
Candidate: What are the primary commands it should support?
Interviewer: Let's implement the core navigation and manipulation commands: mkdir (make directory), cd (change directory), ls (list contents), pwd (print working directory), touch (create an empty file), and cat (view file content). It would also be great to support writing content to a file, perhaps through a simplified echo 'text' > file syntax.
Candidate: Regarding the ls command, should it just list names, or should it support options like ls -l for a more detailed view?
Interviewer: That's an excellent feature to include. The design should be flexible enough to support both a simple listing of names and a detailed format that shows metadata, like whether an entry is a file or a directory and its creation time.
Candidate: How should the system handle paths? Does it need to support both absolute paths (starting from the root '/') and relative paths (from the current directory)?
Interviewer: Yes, that's a key requirement. The system must maintain a concept of a "current working directory" and correctly resolve both absolute and relative paths, including . (current directory) and .. (parent directory).
Candidate: What about advanced file system features like user permissions, ownership, symbolic links, or file locking?
Interviewer: Let's keep those out of scope. The primary focus is on the hierarchical data structure and the command execution framework. We'll assume a single-user environment with no permission constraints.
After gathering the details, we can summarize the key system requirements.
mkdir, cd, touch, ls, pwd, cat, and echo?ls command should support a simple format and a detailed (-l) format.Core entities are the fundamental building blocks of our system. We identify them by analyzing key nouns (e.g., file, directory, path, metadata) and actions (e.g., create, read, move, list) from the functional requirements. These typically translate directly into classes, enums, or interfaces in an object-oriented design.
Let’s walk through the requirements and extract the relevant entities:
This is the core data modeling challenge and is a perfect fit for the Composite design pattern.
FileSystemNode: An abstract base class that represents a generic entry in the file system (either a file or a directory). It defines common properties like name and parent.File: The "Leaf" component in the Composite pattern. It represents an individual file and cannot contain other nodes. It has an additional property for content.Directory: The "Composite" component. It can contain a collection of other FileSystemNodes (both Files and other Directorys), forming the tree structure.This suggests encapsulating each user action into a distinct object, which is the essence of the Command design pattern.
Command (Interface): Defines a common execute method for all command objects.Concrete Commands (MkdirCommand, CdCommand, LsCommand, etc.): Each class encapsulates all the information needed to perform a specific action, such as the target path and a reference to the file system instance.Shell: Acts as the "Invoker" in the Command pattern. It is responsible for parsing the user's string input and creating the appropriate Command object to be executed.A central manager is needed to hold the root of the file system tree and track the user's current location.
FileSystem: This class acts as the central "Receiver" for all commands and the manager of the entire file system state. It is implemented as a Singleton to ensure that there is only one instance of the file system in memory. It contains the logic for path resolution and all core file/directory manipulation operations.FileSystemNode (Abstract Class): Base class for all file system elements. Contains common metadata and methods.File: Represents a content-holding node with data and support for read/write operations.Directory: A container node that can hold child nodes (files and other directories).Command: Encapsulates file operations as command objects for possible undo/redo or logging support.Shell: Responsible for parsing the user's string input and creating the appropriate Command object to be executed.FileSystem: Acts as the root and entry point for managing all operations and holding the directory tree.These core entities define the key abstractions of the in-memory file system and will guide the structure of your low-level design and class diagrams.
ShellFileSystemFile and Directory extend FileSystemNodeDirectory contains a list of FileSystemNodes (files or subdirectories). This is a Composition relationship, forming the tree structure.FileSystem interacts with Directory and File objects to perform operationsThis pattern is used to define a family of algorithms, encapsulate each one, and make them interchangeable. It is applied to the ls command to support different output formats.
ListingStrategy interface defines a common list() method.SimpleListingStrategy and DetailedListingStrategy provide different implementations for how to display the contents of a directory.LsCommand is the context that is configured with a ListingStrategy object. When executed, it delegates the listing operation to its strategy.The ls command is decoupled from the specific formatting logic. We can easily add new listing styles (e.g., a recursive listing strategy) by creating a new strategy class without any changes to the LsCommand or the core FileSystem logic.
This pattern is fundamental to modeling the hierarchical, tree-like structure of the file system. It allows us to treat individual objects (files) and compositions of objects (directories) uniformly.
FileSystemNode abstract class defines the common interface for all nodes in the tree.File class represents a leaf node that cannot have children.Directory class represents a composite node that can contain other FileSystemNodes (both Files and other Directorys).To encapsulate a request (a shell command like mkdir or ls) as an object. This decouples the object that issues the request (Shell) from the object that knows how to perform it (FileSystem).
Command interface declares a single execute() method.MkdirCommand, CdCommand, and LsCommand implement the Command interface. Each object holds all the information needed for its execution, such as command arguments and a reference to the FileSystem.Shell class acts as the invoker. It parses user input, creates the appropriate command object, and calls its execute() method.FileSystem class is the receiver, containing the actual business logic that the commands execute.This makes the system highly extensible. To add a new command (e.g., rm for remove), we only need to create a new Command implementation and add a case to the Shell's parsing logic, without modifying the FileSystem or any existing commands.
The FileSystem class acts as a Facade. It provides a simple, high-level API (mkdir, cp, ls) to the client, hiding the complex internal logic of path resolution, tree traversal, and concurrent locking.
FileSystemNodeAn abstract base class representing both files and directories in the system.
1class FileSystemNode(ABC):
2 def __init__(self, name: str, parent: Optional['Directory']):
3 self.name = name
4 self.parent = parent
5 self.created_time = datetime.now()
6
7 def get_path(self) -> str:
8 if self.parent is None: # This is the root directory
9 return self.name
10 # Avoid double slash for root's children
11 if self.parent.get_parent() is None:
12 return self.parent.get_path() + self.name
13 return self.parent.get_path() + "/" + self.name
14
15 def get_name(self) -> str:
16 return self.name
17
18 def set_name(self, name: str) -> None:
19 self.name = name
20
21 def get_parent(self) -> Optional['Directory']:
22 return self.parent
23
24 def get_created_time(self) -> datetime:
25 return self.created_timeIt stores common properties like name, parent directory, and createdTime. It also provides a method getPath() to compute the absolute path of a node.
File (Leaf Component)Represents a file in the filesystem.
1class File(FileSystemNode):
2 def __init__(self, name: str, parent: Optional['Directory']):
3 super().__init__(name, parent)
4 self.content = ""
5
6 def get_content(self) -> str:
7 return self.content
8
9 def set_content(self, content: str) -> None:
10 self.content = contentInherits from FileSystemNode and adds file-specific data like content. Supports reading and writing content, but does not have child nodes.
Directory (Composite Component)Represents a directory that can contain files or subdirectories.
1class Directory(FileSystemNode):
2 def __init__(self, name: str, parent: Optional['Directory']):
3 super().__init__(name, parent)
4 self.children: Dict[str, FileSystemNode] = {}
5 self._lock = Lock()
6
7 def add_child(self, node: FileSystemNode) -> None:
8 with self._lock:
9 self.children[node.get_name()] = node
10
11 def get_children(self) -> Dict[str, FileSystemNode]:
12 with self._lock:
13 return self.children.copy()
14
15 def get_child(self, name: str) -> Optional[FileSystemNode]:
16 with self._lock:
17 return self.children.get(name)Extends FileSystemNode and maintains a collection of children nodes. Implements methods to add, fetch, and list child nodes, enabling the Composite pattern.
An interface for all shell commands, following the Command design pattern.
1class Command(ABC):
2 @abstractmethod
3 def execute(self) -> None:
4 pass
5
6class CatCommand(Command):
7 def __init__(self, fs: 'FileSystem', path: str):
8 self.fs = fs
9 self.path = path
10
11 def execute(self) -> None:
12 content = self.fs.read_file(self.path)
13 if content is not None and content:
14 print(content)
15
16class CdCommand(Command):
17 def __init__(self, fs: 'FileSystem', path: str):
18 self.fs = fs
19 self.path = path
20
21 def execute(self) -> None:
22 self.fs.change_directory(self.path)
23
24class EchoCommand(Command):
25 def __init__(self, fs: 'FileSystem', content: str, file_path: str):
26 self.fs = fs
27 self.content = content
28 self.file_path = file_path
29
30 def execute(self) -> None:
31 # The '>' redirection character is handled implicitly by the command's nature.
32 # In a more complex shell, this would be more sophisticated.
33 self.fs.write_to_file(self.file_path, self.content)
34
35class LsCommand(Command):
36 def __init__(self, fs: 'FileSystem', path: Optional[str], strategy: 'ListingStrategy'):
37 self.fs = fs
38 self.path = path # Path can be None, meaning "current directory"
39 self.strategy = strategy
40
41 def execute(self) -> None:
42 if self.path is None:
43 self.fs.list_contents(self.strategy)
44 else:
45 self.fs.list_contents_path(self.path, self.strategy)
46
47class MkdirCommand(Command):
48 def __init__(self, fs: 'FileSystem', path: str):
49 self.fs = fs
50 self.path = path
51
52 def execute(self) -> None:
53 self.fs.create_directory(self.path)
54
55class PwdCommand(Command):
56 def __init__(self, fs: 'FileSystem'):
57 self.fs = fs
58
59 def execute(self) -> None:
60 print(self.fs.get_working_directory())
61
62class TouchCommand(Command):
63 def __init__(self, fs: 'FileSystem', path: str):
64 self.fs = fs
65 self.path = path
66
67 def execute(self) -> None:
68 self.fs.create_file(self.path)Each concrete command (mkdir, touch, cd, ls, pwd, cat, echo) encapsulates its own execution logic.
This decouples command execution from the Shell, making the system extensible for new commands.
Defines a strategy for displaying directory contents.
1class ListingStrategy(ABC):
2 @abstractmethod
3 def list(self, directory: Directory) -> None:
4 pass
5
6class SimpleListingStrategy(ListingStrategy):
7 def list(self, directory: Directory) -> None:
8 children = directory.get_children()
9 for name in children.keys():
10 print(name, end=" ")
11 print()
12
13class DetailedListingStrategy(ListingStrategy):
14 def list(self, directory: Directory) -> None:
15 children = directory.get_children()
16 for node in children.values():
17 node_type = 'd' if isinstance(node, Directory) else 'f'
18 print(f"{node_type}\t{node.get_name()}\t{node.get_created_time()}")Two implementations are provided:
SimpleListingStrategy: prints only names.DetailedListingStrategy: prints type (file/dir), name, and creation time.This follows the Strategy design pattern, allowing flexible listing behavior.FileSystem (Client/Manager)A singleton class managing the entire filesystem.
1class FileSystem:
2 _instance = None
3 _lock = Lock()
4
5 def __new__(cls):
6 if cls._instance is None:
7 with cls._lock:
8 if cls._instance is None:
9 cls._instance = super().__new__(cls)
10 cls._instance._initialized = False
11 return cls._instance
12
13 def __init__(self):
14 if not hasattr(self, '_initialized') or not self._initialized:
15 self.root = Directory("/", None)
16 self.current_directory = self.root
17 self._initialized = True
18
19 @classmethod
20 def get_instance(cls):
21 return cls()
22
23 def create_directory(self, path: str) -> None:
24 self._create_node(path, True)
25
26 def create_file(self, path: str) -> None:
27 self._create_node(path, False)
28
29 def change_directory(self, path: str) -> None:
30 node = self._get_node(path)
31 if isinstance(node, Directory):
32 self.current_directory = node
33 else:
34 print(f"Error: '{path}' is not a directory.")
35
36 def list_contents(self, strategy: ListingStrategy) -> None:
37 strategy.list(self.current_directory)
38
39 def list_contents_path(self, path: str, strategy: ListingStrategy) -> None:
40 node = self._get_node(path)
41 if node is None:
42 print(f"ls: cannot access '{path}': No such file or directory", file=__import__('sys').stderr)
43 return
44
45 if isinstance(node, Directory):
46 strategy.list(node)
47 else:
48 # Mimic Unix behavior: if ls is pointed at a file, it just prints the file name.
49 print(node.get_name())
50
51 def get_working_directory(self) -> str:
52 return self.current_directory.get_path()
53
54 def write_to_file(self, path: str, content: str) -> None:
55 node = self._get_node(path)
56 if isinstance(node, File):
57 node.set_content(content)
58 else:
59 print(f"Error: Cannot write to '{path}'. It is not a file or does not exist.")
60
61 def read_file(self, path: str) -> str:
62 node = self._get_node(path)
63 if isinstance(node, File):
64 return node.get_content()
65 print(f"Error: Cannot read from '{path}'. It is not a file or does not exist.")
66 return ""
67
68 def _create_node(self, path: str, is_directory: bool) -> None:
69 print(self.current_directory.get_name())
70
71 if "/" in path:
72 # Path has directory components (e.g., "/a/b/c" or "b/c")
73 last_slash_index = path.rfind('/')
74 name = path[last_slash_index + 1:]
75 parent_path = path[:last_slash_index]
76
77 # Handle creating in root, e.g., "/testfile"
78 if not parent_path:
79 parent_path = "/"
80
81 parent_node = self._get_node(parent_path)
82 if not isinstance(parent_node, Directory):
83 print(f"Error: Invalid path. Parent '{parent_path}' is not a directory or does not exist.")
84 return
85 parent = parent_node
86 else:
87 # Path is a simple name in the current directory (e.g., "c")
88 name = path
89 parent = self.current_directory
90
91 if not name:
92 print("Error: File or directory name cannot be empty.", file=__import__('sys').stderr)
93 return
94
95 # --- Common logic from here ---
96 if parent.get_child(name) is not None:
97 print(f"Error: Node '{name}' already exists in '{parent.get_path()}'.")
98 return
99
100 new_node = Directory(name, parent) if is_directory else File(name, parent)
101 parent.add_child(new_node)
102
103 def _get_node(self, path: str) -> Optional[FileSystemNode]:
104 if path == "/":
105 return self.root
106
107 start_dir = self.root if path.startswith("/") else self.current_directory
108 # Use a non-empty string split to handle leading/trailing slashes gracefully
109 parts = path.split("/")
110
111 current = start_dir
112 for part in parts:
113 if not part or part == ".":
114 continue
115 if not isinstance(current, Directory):
116 return None # Part of the path is a file, so it's invalid
117
118 if part == "..":
119 current = current.get_parent()
120 if current is None:
121 current = self.root # Can't go above root
122 else:
123 current = current.get_child(part)
124
125 if current is None:
126 return None # Path component does not exist
127 return currentActs as the command interpreter for the filesystem.
1class Shell:
2 def __init__(self):
3 self.fs = FileSystem.get_instance()
4
5 def execute_command(self, input_str: str) -> None:
6 parts = input_str.strip().split()
7 command_name = parts[0]
8
9 try:
10 if command_name == "mkdir":
11 command = MkdirCommand(self.fs, parts[1])
12 elif command_name == "touch":
13 command = TouchCommand(self.fs, parts[1])
14 elif command_name == "cd":
15 command = CdCommand(self.fs, parts[1])
16 elif command_name == "ls":
17 command = LsCommand(self.fs, self._get_path_argument_for_ls(parts), self._get_listing_strategy(parts))
18 elif command_name == "pwd":
19 command = PwdCommand(self.fs)
20 elif command_name == "cat":
21 command = CatCommand(self.fs, parts[1])
22 elif command_name == "echo":
23 command = EchoCommand(self.fs, self._get_echo_content(input_str), self._get_echo_file_path(parts))
24 else:
25 command = lambda: print(f"Error: Unknown command '{command_name}'.", file=__import__('sys').stderr)
26 except IndexError:
27 print(f"Error: Missing argument for command '{command_name}'.")
28 command = lambda: None # No-op command
29
30 command.execute()
31
32 def _get_listing_strategy(self, args: List[str]) -> ListingStrategy:
33 if "-l" in args:
34 return DetailedListingStrategy()
35 return SimpleListingStrategy()
36
37 def _get_path_argument_for_ls(self, parts: List[str]) -> Optional[str]:
38 # Find the first argument that is not an option flag.
39 for part in parts[1:]: # Skip the command name itself
40 if not part.startswith("-"):
41 return part
42 return None # Return None if no path argument is found
43
44 def _get_echo_content(self, input_str: str) -> str:
45 # Simple parsing for "echo 'content' > file"
46 try:
47 start = input_str.index("'") + 1
48 end = input_str.rindex("'")
49 return input_str[start:end]
50 except ValueError:
51 return ""
52
53 def _get_echo_file_path(self, parts: List[str]) -> str:
54 # The file path is the last argument after the redirection symbol '>'
55 for i in range(len(parts)):
56 if parts[i] == ">" and i + 1 < len(parts):
57 return parts[i + 1]
58 return "" # Should be handled by argument checkParses user input (like mkdir /home) and maps it to the corresponding Command object. Supports options like ls -l, redirection (echo > file), and error handling for invalid commands or missing arguments.
A demo/driver class that simulates a shell session. Validates the interaction of all components together.
1def main():
2 shell = Shell()
3 commands = [
4 "pwd", # /
5 "mkdir /home",
6 "mkdir /home/user",
7 "touch /home/user/file1.txt",
8 "ls -l /home", # d user
9 "cd /home/user",
10 "pwd", # /home/user
11 "ls", # file1.txt
12 "echo 'Hello World!' > file1.txt",
13 "cat file1.txt", # Hello World!
14 "echo 'Overwriting content' > file1.txt",
15 "cat file1.txt", # Overwriting content
16 "mkdir documents",
17 "cd documents",
18 "pwd", # /home/user/documents
19 "touch report.docx",
20 "ls", # report.docx
21 "cd ..",
22 "pwd", # /home/user
23 "ls -l", # d documents, f file1.txt
24 "cd /",
25 "pwd", # /
26 "ls -l", # d home
27 "cd /nonexistent/path" # Error: not a directory
28 ]
29
30 for command in commands:
31 print(f"\n$ {command}")
32 shell.execute_command(command)
33
34if __name__ == "__main__":
35 main()Runs a sequence of commands (e.g., mkdir, cd, ls, echo, cat) to demonstrate how the filesystem works end-to-end.
Which core entity in the in-memory file system design represents both files and directories in the hierarchy?
No comments yet. Be the first to comment!