Code · LLD machine coding · PhonePe prep

File System

Model files and folders.

4 classes 139 lines View on GitHub ↗

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"));
    }
}