Writing a File Explorer in C
Most of the programming I do day to day is at a level of abstraction where memory management is someone else's problem. That is comfortable but it also means you can go a long time without really understanding what is happening under the hood. I built this file explorer in C partly as a project and partly to make myself think carefully about things I had been taking for granted.
The goal was a command-line tool that simulates a file system: create files and directories, navigate between them, move and delete things, and display the tree structure the way the tree command does. Nothing you couldn't do with existing tools, but writing it yourself forces you to think about the data structure decisions you are usually insulated from.
The Tree Structure
A file system is a tree, so the core data structure is a tree node. Each node holds a name, a flag for whether it is a file or directory, a pointer to its parent, and a pointer to its first child. Siblings are linked: each node also points to the next sibling at the same level. That means navigating down into a directory is one pointer dereference, and iterating through a directory's contents is walking a linked list.
Parent pointers are what make navigation commands like cd .. cheap. Without them, every upward move would require a tree traversal from the root to find the current node's parent. With them, it is just following a pointer. The tradeoff is that you have to keep parent pointers consistent through every insert, move, and delete operation, which adds bookkeeping but is worth it.
DFS for Tree Visualization
Rendering the tree output is depth-first traversal with some state tracking for the visual formatting. Each level of depth gets an additional indent. The tricky part is getting the connector characters right: ├── for nodes that have siblings following them, └── for the last child, and │ for the vertical continuation lines further up the tree.
project/ ├── src/ │ ├── main.c │ └── utils.c ├── include/ │ └── utils.h └── Makefile
Getting that to render correctly required tracking, for each level, whether there are more siblings remaining at that depth. I passed a boolean array down through the recursion to handle the vertical line continuation. It was the kind of thing that looks obvious in retrospect and took longer to get right than I expected.
Memory Management
This is where C makes you earn it. In a higher-level language you create nodes and eventually the garbage collector deals with them. In C you malloc every node and you have to free it. The interesting case is deletion: when you delete a directory you need to free the entire subtree, not just the directory node itself. A recursive free that walks the subtree children-first handles it correctly, but writing that and being sure it does not leak anything required more care than the data structure work itself.
I looked at arena allocation for simplicity — allocate a large block up front, bump a pointer for each node, free the whole arena at once. It would have made the code cleaner. I decided against it because I wanted to practice the discipline of manual per-node allocation and deallocation rather than sidestepping the problem. For a real tool the arena approach would be reasonable.
What It Taught Me
The implementation is not large. The tree operations are all straightforward once you have the data structure right. But writing it in C means every abstraction you reach for, you have to provide yourself. There is no list type, no automatic memory cleanup, no bounds checking. You develop a different kind of attentiveness to what your code is actually doing.
More practically: I now have a much clearer mental model of how parent pointers, sibling lists, and recursive tree operations actually work, which shows up in how I think about similar structures when working in higher-level languages. The low-level version is not the right tool for most problems, but understanding it makes everything built on top of it less opaque.