Director of Engineering at Lever, focused on growing diverse, high-performing teams
Becoming an experienced software developer doesn’t mean knowing the solution to every problem, nor does it depend on knowing the entire system and its many edge cases.
I get it. It feels like you should know this stuff cold, especially when you’re talking to a senior developer who seems to know everything off the top of their head. But ask yourself: is this person working on a problem they already know the answer to? Would they be as successful dropped into a brand new area?
The more senior you are, the more you’ll be expected to take on complex, poorly defined problems, often with very little context. The true secret to increasing your impact is learning how to tackle a problem of any size and breaking it into manageable pieces that you can successfully solve.
I’ve hired, mentored, and promoted dozens of interns and new graduates and I’ve seen first-hand that developing a problem-solving approach that works for you can steer you onto the fastest path to success.
Look around you. Are experienced developers collaborating? Throughout my career, I’ve worked at companies like IBM, Blackberry, Shopify, and now Lever. I’ve observed and practiced many ways to brainstorm and validate ideas. These range from collaborative team activities like pair programming, whiteboarding, Slack brainstorms, and comments on issues and pull requests (PR’s) — to more formal “big company” processes like technical design reviews and architectural review boards.
Get feedback early and often
In most companies, you request a review of your implementation before you ship. New developers often misunderstand the significance of this step. They assume that the goal is to work independently until the problem is completely solved and then expose it for feedback at code review time. This is a huge mistake!
Most experienced developers don’t wait until they’ve implemented the solution to brainstorm and validate the idea with others. They’re transparent and collaborative from the early going.
They’ve learned a little secret of great problem-solvers: asking for feedback early and often leads to more, high-quality ideas and better solutions.
As an experienced developer, there’s nothing I feel worse about than reviewing a PR which has taken the completely wrong approach — it’s no fun telling someone to rewrite most of the code they’ve written. If you engage me upfront, I can help you break down the problem, or give you feedback on some of the code you’ve written so that you don’t repeat mistakes throughout. We can also have face-to-face conversations so that you really understand my feedback, instead of trying to interpret written comments.
Identify a partner upfront
Select a developer on your team to work with right from the start. Ideally, you already have an official mentor or buddy — someone you mesh with that cares about your success and will help you learn. If you don’t have one, find one! If it isn’t your official buddy, or if you don’t have one, proactively ask if they have time and make sure they’re committed to your success. It should be someone that will perform a code review at the end. This person will challenge you on your understanding of the problem, help you through your problem-solving, and ultimately review your code at the end.
Ideally, it should also be someone that has enough context in the area you’re working in that they can answer questions about how something is expected to work. They don’t need to be an expert. In fact, I often find that developers that are just a step ahead of you in terms of development provide the best mentoring because they just recently learned what you need to know and are often excited to apply that learning. They also have the most to gain from mentoring because it helps them to reinforce what they’ve already learned.
There are many ways to make progress on a problem, and they don’t all involve writing code. Experienced developers take a more holistic view of progress, empowering themselves to highlight the strides they’ve made in understanding the problem and validate their thinking with others.
Highlight your progress using new methods
When validating my understanding of the problem and the approach, I’ll most often comment on the issue itself, in whichever issue management tool we’re using — whether that’s Jira, Github, or something else. When I’m working on a more complex problem, I’ll use a document, a gist, or a new issue to capture the design and then subsequently gather feedback. I like these options because they make it easy to collaborate and comment.
Think about the tools your company uses and post your progress where developers spend their time. What tools do developers use most often? How are they being used? What’s the most transparent way for you to communicate and validate your own progress? Who on your team has the most detailed issue and PR descriptions, design documents, and PR comments? Are there any approaches you can mimic? Identify the best tools to capture that progress at every step.
Documenting = Communicating = Collaborating
Here’s a secret: all developers could stand to document their progress more.
It feels counter-intuitive: as if the more senior you are, the less you need to explain yourself in your issues and review requests. But I expect more of my senior developers. The more experienced you are the more I expect you to document your analysis and approach with clarity. As a senior engineer, you’re not just explaining yourself to your code reviewers, you’re modeling for everyone else on the team how you think.
This brings two obvious benefits. First, you might solve problems differently and uniquely from everyone else, and we want to encourage a diversity of approaches to be the best engineering team possible. Second, you are sharing your learning and experience in how to tackle problems, your knowledge of the specific problem domain, and your application of software engineering principles and patterns to solving it.
Validate early and often
I explain this to any developer that complains when late code review feedback causes a rewrite of a large chunk of what they just implemented. Could you have captured different approaches upfront to get feedback before putting the PR up for review? Could you have prototyped something and pair-programmed on it early to gain any context you might be missing?
“A five-minute conversation early on could save you hours of rewrite later.”
— DJ Houghton
You’ll probably be documenting a lot more than others, but I’ve never heard anyone give negative feedback to someone for documenting too much. There have been many times that members of my teams have been praised for the thorough investigative work they do and how seamless it is to work with them. It’s that thoughtful approach that provides the best mechanism for collaboration between teammates.
You’ve been assigned a task — great! The first step is to understand the problem. Is there more than one way to approach solving it? Often, at this stage of your career, the task will tell you exactly what the expected outcome is. Maybe it’ll even include screenshots. However, no one knows what you’ll discover as you start to work on a solution, so don’t expect that it can work exactly the way it was requested.
Why before What
Whether the task is to write a new method or create a new user-facing feature, understanding why the feature exists and what problem it solves is fundamentally important to your success.
Say you’re working on a user-facing component and you’ve been asked to expose a new field in the user interface. The field already exists on the backend and can be set via the API, so you just need to create a place to display it in the user interface. There’s a screenshot — easy to replicate, right?
But why is that field important to the user? Is it always present or are there some cases where it doesn’t exist? Does it have a maximum size? Is input validation required?
This is a great opportunity to understand a new area of the product and brainstorm some of the edge cases you might need to test for. Don’t assume the person that created the issue thought of every case. They may have only included what they knew or remembered. This can be especially true if the issue was created by an external stakeholder, such as support.
But you’re working on this problem now and by the time you’ve shipped, you will be the expert in terms of what the user experiences. Consider the power in this: by understanding problems deeply and collaborating early and often to solve them, you are making your product and your company better. Experienced developers help create amazing products.
Set up your environment
At this point, I’d also recommend starting to set up your local environment to manually test this area. That could mean learning to click through to get to the view you’re editing. It could also mean creating some new data that starts to create some of the permutations you’ll want to test with. You’ll eventually need to do this, and the more you do now, the more likely you are to fully understand the problem and to not run into any surprises.
Validate your thinking
By the end of this step, I’d expect you to have spoken with your mentor to validate your understanding of how the product should work when you’re done implementing it. I’d also expect you to edit the description or add a comment to the original task in your issue management system. Even if it’s virtually the same, that comment should:
This written step is important for many reasons. First, it starts to develop your confidence with regard to understanding and owning the problem. Second, it allows your mentor to ensure that you understood any feedback exchanged when you discussed the problem. And third, you’re part of a larger team! With this documented, others can review your approach, and other stakeholders such as the designer and product manager can see the progress you’ve made and provide feedback.
Perhaps most importantly, you’ve made visible progress (that wasn’t only writing code!). Something to celebrate! 🎉
Once you understand the problem, it’s time to come up with solutions. Consider creating a temporary branch for prototyping. Not all code you write needs to be worthy to ship. This is a great time to use your local environment to make changes to see what works. Which files and methods might you need to change or create? Consider this your opportunity to play around and make changes you’d never consider merging.
There is usually more than a single solution, even if one is the obvious winner. I recommend taking some time to think about at least two approaches. We often implement the easiest, most straightforward way first. But it may not be the easiest to maintain or the most performant.
Once you have at least two approaches, document the pros and cons of each. You can do this in a gist, in a temporary file — anywhere. Develop an opinion on the best solution. Which would you rather implement and support? Once you have an opinion, talk through those approaches with your mentor or buddy. Have you missed any pros or cons? Did you successfully determine the best path forward?
“It was meaningful for me to understand I had the capability/power to form an opinion and advocate for which approach I thought was best! Whereas before, I would come up with some approaches, but wait for my mentor to tell me which path to take. I had to realize that it’s okay to form an opinion to make a suggestion, even if it doesn’t ultimately become the chosen one. That’s learning!” — Stephanie Wong
Most recently, I suggested this approach to someone that just started working with me. The next day, she had a weekly pair programming session scheduled with her tech lead. She prepared her list of approaches, pros and cons, and an opinion of which to select. Later on, the tech lead let me know that it was the most productive pair programming session they’ve had. When I asked my new report to reflect on it, she had this to say: “I’ve always been told to prepare for pair programming sessions, but no one has ever explained what prepared could look like.”
This isn’t to say that this is how you should prepare for all pair programming sessions, because you’ll be at different stages of solving a problem. But if you’re just getting started working on a new problem, this is a great technique that helps you demonstrate how much thought you’ve invested already, and to validate what you’ve discovered.
Now that you know how you’ll implement the solution, consider the components of that solution. Are there multiple pieces you’ll need to write? Could some of these even be shipped independently as their own PRs? Make sure to validate how you plan to break down the implementation with your mentor. They might be able to help you identify other components or dependencies you hadn’t thought of.
Keep it small
Break the problem down into the smallest components you can think of. The smaller each component is, the easier it’ll be to understand, develop and test. What’s more, small code changes are simpler and take less time to review. With large changes, reviewers might feel like they need to set time aside to get in the right headspace. With small changes, they might be able to jump in quickly without losing their flow. You’ll get feedback more quickly — win! Not only will you able to move more quickly, but you’ll also have less feedback to address before you can successfully ship.
Keep it testable
If you decide to ship separate PRs, keep in mind that these should still be whole. For instance, you could introduce a new backend method that the user interface will eventually use in another PR. That method must work independently which makes it a great candidate to separate. Automated tests should be delivered at the same time.
Break it down further
Finally, remember that programming is iterative. As you start implementing your solution, you might learn that some pieces can be broken down further. Should you break it down into even smaller pieces? Recently, a developer on my team discovered that an automated test she wanted to write required the previously existing code to be refactored in order to be tested. Since her PR already had test coverage and had been fully reviewed, she opted to implement the refactor and the automated test independently. As a result of separating that refactor into its own change, she was able to avoid revisiting the code she’d already implemented. She also reduced the amount of time her teammates needed to spend reviewing the same code. In the end, she was able to ship the refactor and new test more quickly and with higher confidence.
Remember, the true secret to increasing your impact is learning how to tackle a problem of any size and breaking it into manageable pieces that you can successfully solve. How will you start to tackle larger problems?
Whether you’re just starting your career as a software developer, or you’re experienced and looking for ways to better collaborate with your team and others, I hope you’ll take some time to think about how this could help you. What’s your ideal mental model? How can you improve communicating and soliciting feedback on your solution to a problem? Be adaptable, and think about how to best apply that approach successfully with your team’s existing tools and processes. What approaches do you use? Let me know in the comments!