I had been programming professionally for less than a year and there I was sitting in daily stand-ups with world-class engineers. The room was filled with people who had previously worked at Google, Amazon, Yahoo, and Microsoft, and who held PhDs in algorithms and machine learning. Decisions happened quickly, and things moved incredibly fast. I was just trying to keep up, and my only option was to learn as fast as I could.
I’d started my programming career at a small flash-sale startup called Mertado, soon after they completed the Winter 2010 batch at Y Combinator. Nine months into my new role, we were acquired by Groupon to help build out their Groupon Goods platform soon after they had gone public. After the dust had settled, I was placed on the team that built the machine learning system to personalize 100 million emails sent to inboxes each morning. I was responsible for taking the personalized outputs from the MapReduce jobs, rendering the results in our HTML email templates, and performing multivariate tests on different audiences in order to increase the conversion rate, where a one percent increase meant millions of dollars in additional revenue.
It turns out that working at a small 10 person startup is vastly different from working at a public company on a large engineering team. Not only was I exposed to new technologies, development workflows, build systems, and enormous codebases, I got first-hand experience observing how high-performing teams deliver software at scale. I started to see how all of my coworkers worked throughout the day juggling so many different tasks, and as I observed I started to learn from them.
What I noticed early on was that they didn’t just sit there and code all day with their headphones on. Yes, my coworkers delivered clean and robust code at a fast pace—but they were good at the entire software development process. The most impactful engineers combine technical depth with a broad set of soft skills, people skills, and product skills.
Some of these abilities come naturally to certain people, but a lot of software engineers struggle with these skills in their careers. The problem is that there aren’t a lot of quality resources available on how to develop these skills, so programmers are left to figure things out on their own—which is unfortunate because sometimes these skills can be the difference in landing a job offer, or getting promoted to an engineering leadership position (e.g. Staff, Principal, Engineering Manager, Director, VP, or CTO).
By far, the one thing that I’ve learned that’s had the biggest impact on my career is that …
…management is not just for managers.
As programmers, there are numerous things we must manage each day in addition to code. The best programmers I know don’t just do a good job of writing code, they do a great job of managing all other aspects of the software development process.
I’ve taken some of the key insights from my upcoming book, Junior to Senior, and have distilled what I’ve learned into key areas that summarize how the best programmers manage the software development process. In no particular order, here’s what I’ve learned.
☝️ These are simply a bunch of guidelines to remember, consider, and practice, so don’t feel pressured to follow each of these every day. Think of this as a list to revisit once a month or annually while you reflect on your career growth.
🔰 A concrete example.
Feeling like an imposter is just that—a feeling. It’s not a syndrome or disorder that you are stuck with. It's a very natural feeling to have, like anxiety or stage fright, but it will pass. Almost every programmer experiences these feelings in their career, even great ones. You should learn to recognize it, understand it in context, and practice mindful ways to reduce it.
Try to understand and accept that you are not your code. It’s easy to get defensive when discussing code, designs, or patterns because you subconsciously associate yourself with the code you produced. This false connection prevents you from looking at code objectively and can lead to conflicts. If you try to distance yourself from your code, then you'll be able to consider feedback from other engineers with an open mind and learn from them.
🔰 A senior engineer gives a blunt suggestion like, “Can’t you just do X instead?” on a code review, implying a bunch of your work that day is unnecessary. Your heart races and you feel incompetent or upset or both. Take a breath and remember, they might be right or wrong, but either way you’re going to make progress and learn.
Further reading:
As programmers, our codebase is our workspace. Be meticulous about keeping your workspace clean and organized. Automated tools like linters and static code analyzers are a big help.
Keep in mind that there may be an additional context that a programmer had to consider when writing code that may not be apparent when reading (or refactoring) it. Try to understand why code was written a certain way.
Code comments should give readers missing context for why the code was written the way it was. Avoid comments that simply describe what the code is doing.
Always remember that most code you write is for humans, not for computers. You and others will have to read and understand your code years after it was written. Most of the time, it’s better to write clear and readable code (even if it’s more verbose) than it is to write clever code (with some exceptions such as low-level system code that may need to be hand-optimized).
Do your best to document and preserve what you learn. Loss of knowledge happens when the original programmers leave the company, get promoted, or move to other projects. When they do, you’ll wish you had more things documented.
Don’t take disagreements on code aesthetics (like code formatting) personally. Value consistency and minimal disruption to existing code over perfection.
🔰 Match existing code styles even if it’s not your personal preference.
🔰 Use auto-formatters like xo, black, eslint, gofmt, etc. whenever possible.
Further reading:
Keep in mind that technologies don’t matter as much as some people think they do. Sure, the choice of language, framework, or database is important, but success or failure is rarely dependent on the technology alone–usually human and team factors play a bigger role in engineering success than technologies.
Be pragmatic, not religious, in picking what technologies to use. Pick technologies that will get the job done and suit the current and future team, not what’s hyped or new, or cool.
Unless there is a significant performance or productivity improvement, it’s usually better to build new projects using existing technologies instead of introducing something new and shiny into the stack. While new libraries, frameworks, and languages are fun to work with, once they are deployed to production they will need to be supported by the business for far longer than most people anticipate. These decisions have downstream impacts for lots of people, even if it’s years down the line, so they should be decided as a team, not by an individual.
Further reading:
Time is your most limited resource, so planning ahead becomes a critical factor. Senior engineers identify projects and tasks that are worth spending time on so they can optimize their projects to those that will have the greatest impact based on the time constraints.
Time-box your work. It’s better to ask for help or move to a different task than to spend too much time running in circles.
🔰 Consider enforcing a “30-minute rule” for yourself: If you’re completely stuck, work 30 minutes longer alone trying to figure that thing out. But then if you’re still stuck, always ask for help. This way you conserve others’ time but also don’t waste your own.
Limiting Work In Progress (WIP) is an effective way to manage your team’s time. Implementing a WIP limit helps identify bottlenecks and allows you to put all your focus on completing in-progress tasks rather than starting too many new things at once.
🔰 Pull requests start piling up because developers are starting new tickets before reviewing others’ code.
🔰 QA engineers get slammed with too many tickets at once, which leads to less thorough testing in order to rush through their backlog.
Time management gets harder and harder the more senior you get. You need to get important things done while not “starving” anything for a time, causing significant problems.
Further reading:
Don’t just assume that you and your manager both understand what is expected from you. It’s better to be explicit and discuss with them what the expectations are for your role.
Trust is an important foundation when working with your manager. It takes forever to build and can be destroyed in an instant.
🔰 Good: Follow through on your commitments. If you tell your boss you’ll do something, do it. (And if you can’t, tell them as soon as you know it’s likely to slip so you both can decide how to mitigate the issue.)
🔰 Good: Ask for help if you’re blocked on a project or if you think you’ll miss an important deadline.
🔰 Bad: Side-stepping your manager and leaving them out of decisions with upper management.
🔰 Bad: Refusing to work on tedious but important tasks or projects when asked to do so.
Adapt to your manager’s style, and help your manager work better with you. If you don’t know their style, ask them (e.g. hands-on, hands-off, prefers face-to-face communication, prefers asynchronous communication, working hours, etc.). You should also communicate your preferred style back to them.
Find the right balance when it comes to communicating.
Giving visibility.
Getting help, direction, or input.
A busy manager is always further away from the day-to-day decisions and technical challenges than they’d like to be. Don’t always assume your boss is aware of the exciting accomplishments you’ve made recently, challenges you’ve overcome, or difficult conversations you’ve managed.
This is especially important if you’re close to, or working towards a promotion to a more senior role.
🔰 Work with your manager to establish expectations on the types of outcomes and behaviors someone at the next level demonstrates, then find ways to let your boss know when you think you’ve demonstrated them.
Further reading:
Understand that some risk is acceptable. The goal is not to eliminate risk, but to reduce unnecessary risks and manage necessary ones. Technical risk comes in many different forms:
Whether a problem can be solved at all.
Whether the code can get shipped on time (individual, team, and management factors).
Operational failures (outages, security breaches, data loss).
Usability failures (latency, poor UX, buggy features, customer churn).
Knowing how to manage risk comes with experience.
Junior engineers are still learning how to identify areas of risk.
Senior engineers know when to allow low probability or low impact risks into the system in order to move quickly.
Document risks that you deem acceptable and give reasons why. While you may understand why a certain decision was made, a little documentation goes a long way to give context to coworkers and future team members.
Further reading:
Do your best to stay calm and professional in the midst of an incident.
Remember that mistakes can be learning opportunities.
Reflect on and document the causes and outcomes of each incident so that you (and others) can learn from your mistakes.
Seek to improve processes to prevent the mistake from happening again.
If you can avoid making the same mistake twice, you will become a better engineer.
Avoid blaming others during incidents. You are a team and everyone is responsible for preventing downtime.
For serious mishaps, after the dust settles, a team should analyze what systems, processes, and decisions contributed to the problem and what changes might help avoid a repeat occurrence. Usually, there are several contributing factors.
Own outcomes rather than tasks. Owning outcomes means making progress even when needs are ambiguous and adapting to problems and changing requirements.
You can’t be outcome-focused if you don’t identify outcome metrics. Before you begin any work, ask yourself the questions: “What are the acceptance criteria for considering this task complete?” and “How will we measure success?”
In order to grow as an engineer, you need to learn to deal with increased accountability for outcomes and increased ambiguity in how to reach those outcomes.
Never hesitate to ask for clarification when there’s ambiguity. Find gaps in the requirements, then ask questions and do your own research to fill those gaps.
Understand that there are different kinds of ambiguity:
Goals: “What are the team’s long-term goals and how do they map to current priorities?”
Execution: “I know what the end goal is, but how do we get there?”
Even without being the most senior technical person, it’s often the person who asks the most questions and who is the most valuable during ambiguous discussions.
Further reading:
At a high level, effective engineering is about shipping software quickly while preserving your ability to keep shipping software in the future. Think of it as moving fast without putting yourself in a situation you’ll later regret—systems that crash, engineers that are tired or unhappy or don’t understand the code, features you can’t ship, and bugs you can’t fix. In practice, this means making lots of trade-offs.
There is no hard and fast rule on most trade-offs. The best you can do is systematically ask the right questions to help make the right decisions.
Speed and process
When do you prefer rapid prototyping (fast feedback and less careful design) versus more design and code review (slow feedback and more careful design)?
Do you emphasize ship-detect-fix (keeping the cost of bugs low) or detect-fix-ship (keeping prevalence of bugs low)?
Should you incur technical debt?
Should you keep systems and modules separated to increase parallel development, or invest in reusable infrastructure, tools, and libraries?
You and others
Do you quickly tackle something alone or work together as a team to design a solution?
How much time do you spend on comments and documentation?
Do you try to enable others to solve a problem, or solve it yourself?
Do you learn something yourself or rely on someone else who already knows?
Technical risk
Do you look for a new algorithm or design that’s not been done before?
Do you use existing systems or write your own?
Do you ship features incrementally or do a big release?
Do you rewrite an old system from scratch?
Further reading:
You are solely responsible for your career development. While your manager or team leads can assist in your learning process, the desire to learn must be driven intrinsically.
Everyone learns differently. Find what works for you.
Learn to identify where you have gaps in your knowledge, and then focus on filling in those gaps.
🔰 If you keep noticing certain words, phrases, technologies, algorithms, or concepts pop up in conversations or what you’re reading, write it down in your notes. Revisit this list when you have some free time and pick a topic to teach yourself.
One of the most effective ways to learn is to read code from programmers that are stronger than you.
🔰 Ask other engineers why they built something the way they did, or how you can best learn how something they built works.
You don’t need to remember everything you learn, you just need to know what to search for when you need specific information—or where to find it in your own notes.
Further reading
Learn from Source Code (an Effective Way to Grow for Beginners) by Nic at Coder’s Cat (coderscat.com)
Learning at work by Julia Evans
Select communication channels and formats best suited to the work itself, the workflows the team uses, and the audience.
🔰 Communicate with other engineers quickly and precisely close to the code (e.g. a quick pull request for an early review), and with product managers or designers in tickets, design boards, or existing or newly shared documents.
🔰 On the other hand, a complex topic where the scope of the problem or the approach is unclear is best discussed over video or on a whiteboard rather than through email or chat messages.
Whenever possible, discuss factual things separately from subjective opinions or preferences. Start with facts to make sure you agree with them, then listen and present your own opinions. Make sure you agree on what the problem is before you debate solutions.
🔰 “We need to add a Redis cache” is a proposed solution. “Latencies on user profile API calls have spiked recently and our Postgres database is sometimes at 80% CPU” is a fact.
Change your communication style depending on the audience. It’s okay to have detailed technical discussions with engineers but avoid needlessly technical terms when talking with non-technical people. You may need to distill complex topics down to understandable concepts for a particular audience. This is a skill not everyone has.
Not all discussions will go the way you want them to. Do your best to stay calm and collected during crucial conversations.
Listening is just as important as talking when communicating.
Critique code, not the engineer, during code reviews. Keep comments constructive and professional.
Further reading:
Use technical debt judiciously. Technical debt can be very dangerous when it’s severe because it impairs the ability to make changes. But it is not always bad, and can even be healthy. It’s just that: debt. Debt can speed you up but you have to pay it off later. There are times when you need that leverage. Unlike financial debt, you can delete code without declaring bankruptcy.
Some reasons to incur technical debt are speed in shipping something that has a business impact (like increasing revenue significantly), fixing an urgent problem (like degraded performance), or dispelling uncertainty by learning quickly if a concept works or not.
Keep track of technical debt in your team’s project management tool, not in your head or in comments in the code. This way technical debt is visible at the project level where it can be documented and included in planning roadmaps.
Try to isolate debt so that it’s easier to delete later. If code is of uncertain value (for example an experimental feature) and isolated (not tightly depended on by other parts of the codebase), debt is often a good idea.
Further reading:
Make code clean, but not so clean it constrains your ability to move and adapt.
Understand that good code approximates the true complexity of the problem at hand—not needlessly complex, and not overly simple.
Good code often has judicious inelegance. Sometimes a quick and dirty approach gets the job done better than spending vital time engineering a solution that doesn’t need to be engineered.
Development methodologies are unique to each company and should evolve with the company's trajectory. There is no silver bullet methodology that will work for every business. What works at one company may not work at another due to culture, team personalities, timing, or management style.
Success is achievable in many, many different ways so rigid dogmatism about processes or tools makes little sense.
The best engineers are passionate and carry lots of opinions, but simultaneously are open to changing their views when presented with compelling facts and alternatives.
Further reading:
Solve the problem on paper first, before writing any code. It’s faster and cheaper to refactor an idea than it is to refactor code.
Write down a tentative solution (even if short and informal) and get feedback from others on the team before finalizing an approach to a problem.
Strive for simplicity and try the easy solutions first, if possible. You can always iterate to a more complex solution next. It’s much harder to iterate to a simpler solution.
Small things are easier to understand, build, test, integrate, deploy, and maintain. Try to keep tasks, functions, classes, pull requests, and user stories as small as possible. Don’t be afraid to break things up into smaller deliverables if needed.
Further reading:
Start with pen and paper by Seth Etter
Attention Is My Most Valuable Asset for Productivity as a Software Developer by Zachary Wade Betz
Getting Things Done When You're Only A Grunt by Joel Spolsky
Stop Overthinking Your Complex Solutions and Start Building Simple Ones by Allen Helton
Write code that is easy to delete, not easy to extend. By tef
If you liked this post or learned something from it, consider joining the waitlist for my upcoming book, Junior to Senior, coming out this October (2022) where I dive into each of these topics in greater detail.
This article was originally published here.
Subscribe for more content like this including software engineering advice, insights, and job opportunities to help software engineers accelerate their careers.
A huge thank you to the following people for helping review and edit this post 🙌