Code · LLD machine coding · PhonePe prep
File System
Model files and folders.
Question
Model files and folders.
- A directory holds files and other directories, nested to any depth.
- Get the total size of any directory (the sum of everything inside it).
- Print the tree with indentation.
- Find a file or folder by name anywhere below a directory.
Design: Composite pattern. File and Directory share FileSystemComponent, so size, display and find
treat both the same way. Run FileSystemDemo.
Directory.java 61 lines GitHub ↗
package com.practice.phonepe.questions.filesystem;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Composite: a directory holds other {@link FileSystemComponent}s - files, or nested directories.
* Every aggregate operation (getSize, display, find) just delegates to each child and combines
* the results, without caring whether a given child is a leaf or another composite.
*/
public class Directory implements FileSystemComponent {
private final String name;
private final List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
boolean nameTaken = children.stream().anyMatch(child -> child.getName().equals(component.getName()));
if (nameTaken) {
throw new IllegalArgumentException(name + " already contains an entry named " + component.getName());
}
children.add(component);
}
public void remove(FileSystemComponent component) {
children.remove(component);
}
public List<FileSystemComponent> getChildren() {
return children;
}
@Override
public String getName() {
return name;
}
@Override
public long getSize() {
long total = 0;
for (FileSystemComponent child : children) {
total += child.getSize();
}
return total;
}
@Override
public void display(String indent) {
System.out.println(indent + "+ " + name + "/ (" + getSize() + " bytes)");
for (FileSystemComponent child : children) {
child.display(indent + " ");
}
}
@Override
public Optional<FileSystemComponent> find(String targetName) {
if (name.equals(targetName)) {
return Optional.of(this);
}
for (FileSystemComponent child : children) {
Optional<FileSystemComponent> found = child.find(targetName);
if (found.isPresent()) {
return found;
}
}
return Optional.empty();
}
} File.java 27 lines GitHub ↗
package com.practice.phonepe.questions.filesystem;
import java.util.Optional;
/** Leaf: a file has no children. Its size is just its own content size. */
public class File implements FileSystemComponent {
private final String name;
private final long sizeInBytes;
public File(String name, long sizeInBytes) {
this.name = name;
this.sizeInBytes = sizeInBytes;
}
@Override
public String getName() {
return name;
}
@Override
public long getSize() {
return sizeInBytes;
}
@Override
public void display(String indent) {
System.out.println(indent + "- " + name + " (" + sizeInBytes + " bytes)");
}
@Override
public Optional<FileSystemComponent> find(String targetName) {
return name.equals(targetName) ? Optional.of(this) : Optional.empty();
}
} FileSystemComponent.java 20 lines GitHub ↗
package com.practice.phonepe.questions.filesystem;
import java.util.Optional;
/**
* The Composite pattern's shared contract. A {@link File} (leaf) and a {@link Directory}
* (composite of many components) both look like "a thing with a name and a size" to any caller,
* so code that walks the tree - getSize, display, find - never needs to know which one it holds.
*
* Deliberately does NOT declare add/remove here. The classic GoF version puts them on this
* interface and has File throw UnsupportedOperationException for them - "transparent" to callers,
* but it means every File carries two methods that never make sense to call. Keeping them on
* Directory only means the compiler stops you from calling directory.add(...) on a File, at the
* cost of callers needing an `instanceof Directory` check if they specifically want to add a
* child to whatever they're holding.
*/
public interface FileSystemComponent {
String getName();
long getSize();
void display(String indent);
Optional<FileSystemComponent> find(String name);
} FileSystemDemo.java 31 lines GitHub ↗
package com.practice.phonepe.questions.filesystem;
import java.util.Optional;
public class FileSystemDemo {
public static void main(String[] args) {
Directory root = new Directory("root");
Directory src = new Directory("src");
src.add(new File("Main.java", 1200));
src.add(new File("Utils.java", 800));
Directory docs = new Directory("docs");
docs.add(new File("README.md", 400));
Directory nested = new Directory("nested");
nested.add(new File("deep.txt", 50));
docs.add(nested);
root.add(src);
root.add(docs);
root.add(new File("build.gradle", 300));
// uniform treatment: root.getSize() doesn't care that some children are files and some
// are directories - it just asks each child for its size and adds them up
System.out.println("Total size: " + root.getSize() + " bytes");
System.out.println();
root.display("");
System.out.println();
find(root, "Utils.java");
find(root, "deep.txt");
find(root, "missing.txt");
}
private static void find(Directory root, String name) {
Optional<FileSystemComponent> result = root.find(name);
System.out.println("find(\"" + name + "\") -> " + (result.isPresent() ? "found" : "not found"));
}
}