5 Years Ago: A Mid-Level Engineer’s Shock Upon Inheriting a Monorepo "Anchor"... Powered by Bazel

Five years ago, when I was still a mid-level engineer used to simple repositories (polyrepos) where running code was as trivial as npm run dev or go run main.go, my team was handed over a massive cluster of projects.
The Tech Lead from the outbound team handed over the repo with a piece of advice whose sheer "heavyweight" nature I didn't fully grasp at the time: "Everything is in a single repo. We use Bazel to build it."
I eagerly ran git clone, excitedly opened up the codebase, and... completely froze. There were no familiar script files. Staring back at me were dozens of files named WORKSPACE and BUILD.bazel scattered across every single directory. That was the beginning of a grueling yet most rewarding chapter in my career.
1. The Culture Shock of "Explicit Dependencies"
To a mid-level engineer back then, my mindset was simple: if I wanted to use a library, I just imported it; if it was missing, I added it to package.json or go.mod. But stepping into the world of Bazel, that rule was completely shattered.
Bazel operates on a strict philosophy: Hermetic Computing. This means it does not trust anything pre-existing on your local machine.
Want a file in
service-ato call a helper function inshared-lib? You couldn't just type a relative path. I had to go into theBUILD.bazelfile ofservice-aand explicitly declare: "I am going to depend on the target//packages/shared-lib."Forgot to declare it? Bazel would immediately throw an error at build time, even if your IDE recognized the code perfectly fine.
During the first week of the handover, I spent half my time just... learning Starlark (a Python-like language) to write those BUILD files. It was incredibly frustrating, and I kept asking myself, "Why on earth do people make things so hard on themselves?"
2. And Then... The "Magic" Happened as the System Expanded
Once I climbed over the configuration hurdle and got into the groove of fixing bugs and shipping features, I realized Bazel wasn't a tool for self-inflicted torture—it was an engineering masterpiece designed for large-scale monorepos.
The system we inherited had nearly a dozen services of various sizes. In a typical CI/CD pipeline for a monorepo, every push would mean waiting an hour for the system to re-test and re-build everything. With Bazel, it was a completely different story:
Next-Level "Affected Builds" and "Caching"
Bazel maps out the Dependency Graph of the entire repository like the back of its hand.
When I modified a single line of code in
service-frontend-a, Bazel knew perfectly well that this change had absolutely nothing to do withservice-backend-borservice-mobile.Upon running the build command, it only built
service-frontend-a.As for the rest of the services? Bazel pulled them from the cache (even a Remote Cache shared between my machine and the CI/CD server) at speeds measured in milliseconds.
Bazel’s tagline rings true: "Fast and Correct. Choose two." With Bazel, you actually get both. A build is reproducible and consistent; the dreaded "It works on my machine but breaks in production" phrase simply vanished.
A First Taste of a Polyglot Monorepo
The project handed over to us wasn't built on a single language stack. It had a TypeScript frontend, a core service in Go, and several automation scripts in Python.
Without Bazel, our team would have had to install multiple runtimes (Node, Go, Python) locally and deal with painful environment management. With Bazel, it automatically fetched the exact versions of Go and Node into isolated hidden directories and ran them. I only needed to execute a single command: bazel test //..., and all test cases across all three languages ran concurrently and flawlessly.
Epilogue
Five years ago, taking over a monorepo managed by Bazel felt like a driver used to a small-displacement manual car suddenly being thrown into the cockpit of a Boeing 747. It was overwhelming—the control panel was packed with buttons, and a single mistake in a config file could ground the entire "airplane."
Yet, that trial by fire with Bazel is exactly what forged an incredibly disciplined mindset in me regarding Dependency Management and Build Systems. It forced me to look under the hood of the architecture: to understand how my code is actually compiled and how CI/CD pipelines truly operate. It became an invaluable stepping stone, ensuring that I would never be intimidated by any enterprise-grade, massive-scale system ever again.
Related reading

Breaking the Rules Safely: When a Tech Lead Purposefully Violates the Liskov Substitution Principle (LSP)
SOLID is not a religion, and design principles are not immutable commandments. From the perspective of a battle-tested Tech Lead, sometimes deciding to bend the Liskov Substitution Principle (LSP) is a mature choice to keep the system alive. Let’s analyze 4 classic trade-off scenarios and the art of safely isolating the 'toxic code'.

Less Greedy Code, Less Misery: The Power of SRP Through a Battle-Tested Lens
Most critical production bugs don't come from bad algorithms—they come from cramming too many responsibilities into one place. This is a deep dive into the Single Responsibility Principle (SRP) from a pragmatic, real-world perspective: how the "convenience trap" breeds God Objects, why a typical order-processing function becomes a maintenance landmine, and how a Senior refactors it into clean, testable layers. Clean architecture isn't free—but when your project scales to 100 features, it's the very thing that keeps it from collapsing.

The Silent Convergence: Why Go, Rust, and Kotlin Are Headed in the Same Direction — And Why It Matters
It’s not that Rust is objectively better than Java. Nor is it that Go will replace C. Rather, the way we think about writing code has silently shifted over the past fifty years — and you are standing right in the middle of that transformation.
Discussion
0 Comments
Be the first to start the discussion.