At Atomic, we do a lot of pair programming. When pairing, there is usually a “driver.” The driver “steers the car,” meaning they actively write code and “drive” through the codebase. There is also the “navigator.” The navigator isn’t actively writing code.
On the surface, being the navigator might sound a bit boring. After all, the navigator isn’t zooming through the code base or writing up the next test case. But there are a lot of meaningful contributions to make as an involved navigator. Below, I’ll go over some ways to contribute to great code as a navigator.
Being a “Human Linter”
One of my pairs would call me a “human linter.” Though, hopefully, the driver has a linter installed and is writing tests, a human can still decipher syntax errors better than a compiler and catch small logic errors faster than a test.
Sometimes, when programming by oneself at the end of a long day, a programmer might spend 10 minutes tracing an error down to realize they, for example, accidentally typed an OR instead of an AND. A navigator can help with this by saying, “Hey, did you mean to make that an AND?” as soon as the driver types the culprit code. Or, the navigator might point out: “Hmm, the syntax of that type looks a little off. I think you meant to use a
| instead of a
Having an engaged navigator can prevent the driver from wasting time on pesky syntax errors or little logic mistakes.
Documentation: “Let me look that up!”
In software development, there is no shortage of docs to read. Most libraries, languages, platforms, and deployment tools have a plethora of docs explaining their intended usage, showing the preferred syntax, and providing examples. In my experience, pulling in a new tool usually goes way better if you understand its intended use, take the time to read the instructions, and look at a couple of examples.
As the driver gets the new tool up and running, the navigator can verify the tool is being used as intended. For example, as a navigator, I might say, “Looks like the table component is rendering! I see that the docs say that, for performance reasons, the rows should be memoized before getting passed in — let’s update that.”
Or, if the driver is trying to remember the syntax for a language, the navigator can reference the docs and look up the syntax. This lets the driver stay focused on writing code instead of switching back and forth between looking up syntax and the problem at hand.
Both the driver and navigator might have the docs pulled up. However, having the driver focus on getting the thing up and running and the navigator focused on reading docs and looking up examples allows the driver to stay more focused on the code.
In-the-Moment Code Review
When actively pairing, the navigator is essentially doing code review throughout the development process. Little things might be missed or feel a bit petty to bring up in a more formal code review (for example, renaming a test, tweaking the structure of a component, changing a variable name). But those things come up naturally when pairing. And some issues — especially when pairing with a more junior developer — benefit from being brought up earlier when it is easier to pivot and switch approaches instead of after the fact.
Focusing on the Big Picture
As a navigator, you’re probably spending less brainpower remembering a Vim command or thinking through the logic of a specific function, so you can focus more on the big picture.
When navigating, I think about how the function/component/endpoint at hand ties into the rest of the codebase. I usually have a copy of the codebase pulled up on my machine, and I try to answer these questions:
- Are we doing something similar anywhere else? If so, is the current code we’re writing consistent with our previous approach? Are we re-writing logic that we could reuse?
- What “layer” should this work live at? For example, is it specific enough that should it live on the frontend next to the component? Or is it business logic that could be encapsulated?
- Is the thing we are implementing consistent with the business requirements and designs?
- Once we finish up this function/component/endpoint, where are we going to use it? In other words, is this “puzzle piece” going to fit into our “puzzle” the way we want?
Teaching & Learning
As the navigator, you get to see how talented developers implement solutions from beginning to end. When driving, there’s pressure to write code and get something working. As the navigator, there’s a little less pressure, and you can decide what to focus on at a given moment.
At first, you can focus on the syntax, looking up docs, and catching syntax errors. Then, as you become more confident, you start to focus more on the bigger picture and ask more of the “why” questions. “Why break the problem down like this?” or “Why use this library?” or “Why fetch data at that layer?” You can also propose alternative approaches. Most of my learning at Atomic has been via being a navigator — initially observing how senior developers broke down hard problems, becoming more and more engaged in checking code and component logic, and gradually focusing a bit less on the syntax and a bit more on the big picture.
And, no matter how experienced one becomes, there are always more tips and tricks to learn in tech. As an observant navigator, you can always pick up a few new tricks from the driver and impart some of your own.
Building a Relationship
Lastly, being an engaged navigator allows for the building of a relationship in the way code review or Slack messages do not. I enjoy getting to know my pair — who they are and how they think about things and solve problems. Whether I’m driving or navigating, I appreciate the shared responsibility and solving interesting problems together.
Those are all ways I’ve found to be an engaged navigator. Thanks for stopping by, and happy navigating!