Object-Oriented Design Interview

Course Content

Object-Oriented Design Interview

14 sections · 29 lessons

File Search: requirements and the file-tree Composite


A file search tool looks like a small utility, and that is exactly why interviewers like it: with five or six classes and no domain complexity, nothing hides a weak model. This lesson covers the prompt, the requirement that decides the score, and the first of two places the Composite pattern appears — the file tree itself.

Four questions that change the modelDesign afile searchLive walk or an index?Can criteria combine?Follow symbolic links?Return all, or stream?
"Combined how" is the question that reveals this is a Composite problem, not a loop with flags.

What makes this problem a favourite

It is small. There are five or six classes and no domain complexity, so nothing hides a weak model. And it has one clear right answer that most candidates miss on the first attempt: the same structural idea — Composite — applies twice, once to the file tree and once to the filters. A candidate who spots the second application is visibly stronger than one who does not, and it takes fifteen seconds to check.

It also punishes feature-counting. A candidate who implements eight filters with an if-else selector scores below one who implements three filters that combine arbitrarily.

The four questions that change the model

1. "Which criteria do I need to support?" Get a list: name (exact, prefix, glob?), extension, size (greater than, less than, between), modification date, permissions, owner. The specific list matters less than the count — more than two means the extensibility requirement is real.

2. "Should criteria combine with AND and OR? Nesting?" This is the decisive question. find . -name "*.log" -size +10M is an implicit AND. If the answer includes OR and nesting — "all .log files, or anything over 100 MB" — you need the filter Composite from The filter design rather than a list of filters applied in sequence.

Ask it explicitly, because if the interviewer says "AND only" you can write ten fewer lines, and if they say "arbitrary combinations" you know what the round is about.

3. "Is the filesystem in memory, or am I calling the operating system?" Assume an in-memory tree behind an interface. The traversal logic is identical, and it makes the design testable without a real disk. Say this: "I'll model the tree as objects behind an interface; a real implementation would wrap the operating system's directory calls."

4. "Case sensitivity, hidden files, and symbolic links?" Small questions that show you have used a filesystem. Case sensitivity should be a filter option, not a global. Symbolic links introduce cycles, which the section's extensions handle.

Assumptions this lesson makes

  • An in-memory tree of files and directories behind an interface; a real implementation would read the disk.
  • Filters: name (with glob), extension, minimum and maximum size, modified after a date.
  • Combinations: AND, OR, NOT, arbitrarily nested.
  • Trees up to a few hundred thousand entries — deep enough that recursion depth is worth mentioning.
  • Single-threaded search; parallelism appears in the extensions.

Requirements

Functional requirements

Text
1. Walk a directory tree from a given root2. Match an entry against a criterion: name, extension, size, modified date3. Combine criteria with AND, OR, and NOT, nested to any depth4. Return every matching file5. Support a search that stops early (first N matches)

Non-functional requirements

One matters more than everything else, and it is not stated in the prompt:

The requirement not stated in the promptWritten on the board• Search a tree by name and extension• Search by size and modified date• Combine criteria with AND and ORSaid out loud, and scored• A new criterion adds a class, edits none• Traversal never learns what a filter is• Depth is bounded only by the filesystem
The unstated requirement — extensibility of criteria — is the one the whole design is graded against.

N1. Adding a new criterion must not modify any existing class. This is the Open/Closed Principle (the SOLID reference), and this problem exists to test it. If adding "search by owner" means editing an enum, a switch, and the search runner, the design has failed regardless of how many filters it supports.

N2. Deep trees must not blow the stack. A recursive walk on a tree 10,000 levels deep overflows. Real filesystems are rarely that deep, but a symbolic-link cycle makes any tree infinitely deep, which is why this pairs with the cycle-detection extension.

N3. Directories that cannot match should not be descended. If a filter says "only inside /var/log", walking /home is wasted work. Traversal and filtering, in code covers where to apply this and why it is subtle.

What is cut

Text
CUT (named, not forgotten)- Content search (grep) — mentioned in extensions- Indexing and a persistent index (that is a different problem: locate, not find)- Permissions and ownership filters (same shape as the ones built; no new ideas)- Concurrent traversal

Note the third one specifically. Saying "owner and permission filters are the same shape as the size filter — one more class each, no new design" is better than implementing them, because it shows you understand that the design is finished, not the feature list.

The requirement to say out loud

Read the non-functional list back with this framing:

"I want to call out the requirement that isn't in the prompt: adding a new criterion should be a new class, not an edit. I'm going to design for that rather than for the number of filters, because the filter list will grow forever and the code that walks the tree shouldn't."

That sentence, in the first ten minutes, tells the interviewer you know what the problem is testing.

Finding the objects

The candidate nouns

File, directory, filesystem, entry, name, extension, size, modification date, filter, criterion, search, result, path.

The first Composite: the file tree

A file and a directory are different things. A file has bytes and a size; a directory has children and no size of its own. The naive model gives them separate classes with no relationship, and then the traversal code has to keep asking which one it is holding:

Java
// The version to show first, and then improvepublic void search(Object entry, List<File> results) {    if (entry instanceof File f) {        if (matches(f)) results.add(f);    } else if (entry instanceof Directory d) {        for (Object child : d.getChildren()) search(child, results);   // children of what type?    }}

Directory.getChildren() returns a list of what? It has to hold both files and directories, so it needs a common type — and once you have written that common type, the instanceof checks disappear.

Composite (The seven patterns that actually appear) is exactly this: a common interface, a leaf, and a container that holds the interface type and recurses.

Java
public abstract class FileSystemEntry {    protected final String name;    protected final Instant modifiedAt;    protected Directory parent;    public abstract long size();          // file: its bytes; directory: sum of children    public abstract boolean isDirectory();    public String path() {        return parent == null ? name : parent.path() + "/" + name;    }}public class FileEntry extends FileSystemEntry {    private final long sizeBytes;    public long size() { return sizeBytes; }    public boolean isDirectory() { return false; }    public String extension() {        int dot = name.lastIndexOf('.');        return dot < 0 ? "" : name.substring(dot + 1).toLowerCase();    }}public class Directory extends FileSystemEntry {    private final List<FileSystemEntry> children = new ArrayList<>();    public long size() { return children.stream().mapToLong(FileSystemEntry::size).sum(); }    public boolean isDirectory() { return true; }    public void add(FileSystemEntry child) { child.parent = this; children.add(child); }    public List<FileSystemEntry> getChildren() { return Collections.unmodifiableList(children); }}

Three things this buys, and they are worth naming out loud:

  • The recursive walk becomes three lines, because there is no type checking. Traversal and filtering, in code shows it.
  • size() works on a directory — it is the sum of its children, computed by the same recursion. That is Composite doing real work rather than being ceremony.
  • path() works for free by walking parents upward.
«abstract»FileSystemEntry- name: String- createdAt: Instant+ size() long+ accept(Visitor)File- bytes: long- extension: String+ size() longDirectory- children: List+ size() long+ add(Entry)0..*notationinheritance — is aaggregation — has, but can outlivea Directory holds Entries — so itholds Directories. That loop is thecomposite pattern.size() on the tree/projectsize() = 12 KBsrc/= 9 KBmain.py3 KBa.py5 KBb.py4 KBThe caller asks the root for its size and never learns whether it is talking to a file or afolder. That uniformity is the whole point of the pattern.
Directory both extends the abstraction and holds it — the loop in the diagram is what makes recursion possible.

The rest of the model

ClassIts one-sentence job
FileSystemEntryThe common type: anything with a name, a size, and a parent
FileEntryA leaf with bytes
DirectoryA container of entries, which recurses for size
FilterAnswers yes or no for one entry — The filter design
FileSearcherWalks a tree and returns the entries a filter accepts

Five classes plus filter implementations. path, name, size, and extension are all fields or derived values, not classes.