How to Ask Better Questions

39 minutes, 7 links


Updated August 7, 2023

Asking questions is a natural instinct in humans. In fact, it’s so ingrained in our DNA that we begin asking questions about anything and everything from the earliest days of our childhood. Kids are curious about how the world works, so they ask questions as a way to help them make sense of the things they see. They’re full of endless questions, but there’s one that is simple, yet so powerful at the same time: why?

  • Why is the sky blue?

  • Why do I have to eat my vegetables?

  • Why do I have to go to bed right now?

  • Why can fish breathe underwater but I can’t?

  • Why is my sister taller than me?

Kids ask these questions because they see something but don’t understand why it is the way it is. Children’s brains are still developing, and they don’t yet understand why they have to do certain things or why they can’t do other things. So, they do the only thing they know how to do, which is ask questions.

Despite the fact that most answers to those questions are way more complex than what a child can understand, the mere fact that they’re asking “why” is such a profound step in their learning process.

Asking why indicates a certain level of curiosity for understanding. They don’t just accept something for what it is, they want to know the reason behind it. When you ask why, you’re taking an active approach in gathering knowledge about something. You want to get to the truth because when you understand the reason behind something, you can build upon that foundation, connect the dots between ideas, and expand your knowledge.

As a software engineer, it helps to be curious about your craft. Just like a child asks questions to understand something about the world around them, you should ask questions about the codebases, systems, and team processes you work with on a daily basis. There’s so much knowledge to be learned, and sometimes asking the right questions is the only way that wisdom will be passed down from senior engineers to junior ones. Your teammates are some of the most valuable resources you’ll have during the course of your career. Combined, they likely have decades of experience between them, so it’s a good idea to tap into that knowledge if you can.

exampleA curious software engineer might ask:

  • Why is our data processing pipeline a series of asynchronous jobs instead of one big calculation?

  • Why did we choose NodeJS over Ruby or Python for our backend?

  • Why do we spend so much time grooming our ticket backlog when we could be building?

  • How does our fraud detection system differentiate a legitimate purchase from a fraudulent one?

  • Why did we decide to use the builder pattern here instead of calling the constructors directly?

Asking these kinds of questions not only helps you learn and understand how the systems around you operate, it also gives you context behind some of the decisions that were made in the past, even before you joined your team, which will help you understand why your team might do things a certain way.

You’ll learn a lot just by asking questions, but keep in mind that there’s a fine line between being curious and being annoying. Just as parents often get fed up with the never-ending questions about “why this” and “why that,” your coworkers may get tired of having to answer all of your questions. Remember, asking someone a question may interrupt their train of thought, or they may need to stop what they’re doing in order to explain the answer to you. It’s possible to ask too many questions, so it’s important to find the right balance of figuring something out on your own versus asking someone to explain something to you.

Unlock expert knowledge.
Learn in depth. Get instant, lifetime access to the entire book. Plus online resources and future updates.
Now Available

Here are a few examples:

don’t“Why isn’t my code working?” As a junior engineer, this is one of the worst things you can ask your coworkers. You’re asking them to help do your work for you, which probably won’t go over well with some of the senior engineers.

doAsk this instead: “I’m having trouble getting my solution to work the way I want it to. I’ve tried X, Y, and Z, but I’m still not able to get it right. I could use a second pair of eyes on it to make sure I’m not missing something obvious. Do you mind helping me out?”

don’t“Where do I start with this task?” This is another one that will probably upset some of your teammates. It shows that you haven’t put in any effort to research the task on your own. While you may think you’re being efficient with your time by getting a head start on your task, you risk losing the trust of your teammates by showing them you’re not doing your research.

doAsk this instead: “I’ve read the ticket and searched the codebase for where to begin, but I’m unsure if I’m understanding it correctly. Is [insert file name] the correct place in the codebase where this change needs to be made? If not, would you mind showing me where I should be focusing?”

To ensure you’re not interrupting your coworkers too often, it’s good to do your own research prior to asking your question.

As a junior engineer, it can be intimidating asking senior engineers a question, especially if you’re just starting out in a new position. Half the battle is just figuring out what to ask, and that requires a good understanding of the problem you’re trying to solve. Once you know what to ask, you’ll also need to think about how to ask it and who you should ask. Let’s look into the different types of questions you may find yourself asking.

Asking for Help

There will be times when you’ll hit a dead end with your code. Either you’re getting an error or your code just isn’t doing what you think it should be doing. It’s tempting to drop everything and ask a coworker for help, but you shouldn’t ask them without being prepared.

Knowing what to ask is often the hardest part. You’re struggling to get the code to work because you don’t have a good grasp on the problem you’re trying to solve, but where do you even begin asking for help?

A simple thing to remember is that you should be asking for advice, not a solution. Don’t expect your coworker to fix your bug or code your feature for you. While most people will be happy to help you arrive at a solution, no one wants to do your work for you.

When you ask someone for help, you’re asking someone else to take time away from their own projects to help with yours. Switching context back and forth is hard when working on something that requires deep focus, and you need to respect your coworker’s time if you want to build trust with your team. Try not to waste their time with a question that you can easily find by querying a search engine.

So how can you do that?

  • Come prepared with a clear explanation of the problem you’re trying to solve. If you can’t explain the problem clearly, you’re not ready to ask for help yet.

  • Give examples for how to recreate the problem if you’re able to.

  • Show them what solutions you’ve already tried so they can eliminate potential options.

  • And remember: ask for advice, not a solution.

The more effort you put into asking the question, the less time your coworker will need to get up to speed with the problem. You’ll help your coworker understand the problem quicker, which means they’ll be able to help you quicker.

What to Ask

Don’t be discouraged if you’re having trouble explaining what the problem is that you’re trying to solve. Oftentimes, it’s a complicated explanation involving lots of context before you can even get to the problem itself. It may be hard to find the right words to explain what the problem is, so it’s important to take the time to understand the problem completely before asking for help.

If you don’t understand the problem well enough to explain to someone else, take some time to review everything again. Reread the description of the task to make sure you’re understanding it correctly. In some cases, it helps to write the problem down on paper first or type it out in your notes. Getting the thoughts out of your head will help you organize them, and sometimes, the solution will come to you during this time.

Consider the following:

don’t“I can’t figure out why this isn’t working. Can you help?”

do“I can’t figure out why my click handler isn’t executing when I’m clicking the button. I’ve set up my event listeners, but they’re not picking up the click event. Can you help take a look to see if I’m missing something?”

The second example conveys more information to the person helping you, and it gives them enough context to know what you’re trying to do and where you need their help. Try to be specific in the details of your question, and the more context you can provide, the better.

Narrow Down the Problem

It sounds silly, but if you’re getting an error, make sure to read the error message. I mean really read it. Read it multiple times to make sure you’re fully understanding what the compiler or interpreter is telling you. Sometimes the answer is staring right at you from the error message, but you may not even realize it because you’re skimming over it.

If you have a stack trace, follow the code path line by line. The compiler is giving you hints at what the issue is and where you can find it, so follow the trail. Pull up each file and go to the line listed in the stack trace. Do this for the last few functions that executed before you hit the error.

If there isn’t a stack trace available, try modifying the code in your local environment to add some logging. Add log statements to print out the information you need to figure out what’s happening.

Better yet, use a debugger if you have one set up. Being able to step through the code and see the values change feels like superpower when tracking down bugs. Not only that, but you’ll be able to see how your logic works in real time, with real inputs. You’ll come away with a better understanding of what the code is doing and how that part of the system works.

Reproduce the Issue

Next, make sure you can reproduce the issue before asking anyone for help. If you can’t reproduce the issue, then you’ll just be wasting other people’s time by asking for their help. You’ll want to be able to show whoever is helping you how to reproduce the issue to give them context on what you’re trying to solve. After all, if you’re not able to get your application in the right state to trigger the error, how will you even know that you fixed it?

important There are exceptions to this rule, however. For example, if you’ve spent three days trying to reproduce an issue and you’re still not able to, it might be good to ask for some help. Use your best judgment and only ask for help if you’ve put in significant effort to reproduce the issue and you’re still not making any progress.

Try different inputs, both good and bad. Does the error only show up with specific input types, or does your program throw an error regardless of the kind of input. Check the same thing but with different application configurations as well. For example, does the export functionality break for all file types or just a subset of available file types your application provides?

Give Context

Give some background context about the problem to the person helping you out. Don’t assume they know what problem you’re trying to solve, and don’t assume they’re familiar with the part of the codebase you’re asking them about. If possible, try to give them some context around the problem to help get them up to speed. Show them what solutions you’ve already tried, and explain to them where you’re getting stuck. Are there solutions you’ve been able to eliminate? Give a quick explanation about how you were able to determine that certain solutions won’t work, and why.

The more information, the better, so try to be as specific as possible. Does your data need to be in a certain state in order to reproduce the bug? Let your coworker know. Are you able to reproduce the bug with all third-party integrations, or just a specific one? Let them know. Give as much context as you can, because that will help them narrow down the problem and eliminate potential scenarios.

Do Your Research

There’s a misconception among young developers that experienced programmers know so much that they rarely need to Google anything, when in reality, it’s just the opposite. All programmers use search engines and Stack Overflow to help answer their questions, even the senior and staff engineers.

Humans can only retain so much information before we begin to forget things. Our brains can hold vast amounts of knowledge, but there’s still a limit to how much information we can retain.

Chances are you’re not the first person to encounter an error message or run into an issue with your implementation. There’s a good chance that there are plenty of people who have posted similar questions to online communities or written blog posts on the same issues you’re running into.

Try searching Stack Overflow, reading blogs, or reading GitHub issues and pull requests. Those are the best places to start. Be descriptive in your search queries, but try to remove any specific file or variable names that are unique to your code. You may need to try a few different search queries before you find what you’re looking for, but if you’re able to explain the problem correctly with your query, you shouldn’t have any trouble finding someone else who also ran into that same issue.

caution If you can find a solution to your problem by searching the internet, that’s great. That means you didn’t have to ask any of your teammates for help! But be careful though. While it’s tempting to copy and paste solutions you found on the internet, there are many examples online that may not be the most efficient code or the most secure, and the last thing you want to do is compromise the security of your codebase using a solution you found on the internet. If you’re unsure of something, ask a teammate to help you evaluate the quality of a solution first.

Search Internal Documentation

It’s easy to forget that your team’s internal knowledge base is a good place to look for answers. Engineers often document how a part of the system works, including any known issues, limitations, or other considerations and trade-offs they made when designing a feature.

If you’re running into issues setting up your environment, getting your build to compile, or running your test suite, the solution to your problem may be documented in the internal knowledge base. It doesn’t hurt to take a quick look there first.

While you may find an answer in the internal documentation, that page may be out-of-date by the time you find it. If you find an answer, try to find the original author and ask them to verify that it’s still up-to-date. If the original author is no longer on your team, ask a teammate who you think would be familiar with that part of the system.

There’s a lot to consider when asking for help, but in some cases, you can avoid it altogether if you’re able to leverage the tools around you.

Sometimes, you’ll run into scenarios where you’ll need to ask questions to gather more information before you can proceed. In these situations, you’ll need to ask questions to clarify something.

Asking for Clarification

When you’re working on a new feature, a bugfix, or almost any project, you may not have all the information you need in order to complete the task. The devil is in the details, and an experienced senior developer knows when they need to ask the project owners to clarify small but important details before proceeding.

Sometimes, the small details won’t change the implementation much, but other times, a slight clarification to the project’s requirements could have major ramifications for the design and implementation of your solution.

Here are a few things to consider when you may not have all the information you need to move forward with a project.

Break Down the Requirements

Before asking for clarification, first try to break down the requirements into the smallest pieces of individual features you can. Try to think about your task in terms of the following requirements:

Inputs. Determine which inputs are required, which ones are optional, and what the expected values or ranges should be for each input. This helps you dig deeper into what inputs you should expect so that you can build logic around them to prevent bad data from getting into your system.

Try to think in terms of how the system will handle different scenarios. You will naturally come up with some additional questions for your teammates or the product owner if you can think of edge cases where a user may enter some unexpected input.

  • The name field is only one input field in the design. What if someone only gives us their first name when we need their full name? Should we break it into two fields so we can make each one required?

  • Are we verifying emails to make sure they are deliverable, or is basic email format validation good enough?

  • Will the number always be an integer? Or should we expect decimals too?

  • Will the data streaming from the widget’s sensor always be in a range between 0.0 and 1.0? What should we do if we receive a negative number from the sensor? And can this value be null if there is an issue with the sensor?

The more clarification around your inputs, the more robust your validation logic and error handling will be. Common errors occur when a program encounters unexpected inputs, so if you can ask good questions to clarify any missing requirements, you’ll be able to prevent errors before they happen.

Outputs. Double-check what type or format your outputs should be in, especially if you expect the output to become the input in another program or function. This is easier if you’re using a strongly typed language, and it is critical if you’re using a scripting language.

exampleHere are some examples of what to ask when clarifying output requirements:

  • Should we output the data in CSV, TSV, JSON, or give the user options for all three?

  • Does the data we’re outputting contain user-submitted content? Does our templating framework escape outputs automatically? How can we avoid XSS attacks?

  • How many results do you want in the API response? Should we paginate the results?

  • What should the precision on our floats be when we format them during rendering?

Error handling. Reliable software programs contain robust error handling. Reliable programs gracefully recover from certain errors and display helpful information to the user if the program is unable to proceed. In order to write code like this, you have to anticipate ways in which your program can fail. If you understand all of the things that can go wrong with your code, you can then add logic to handle those errors gracefully or to exit the program and display a helpful error message to your users.

exampleHere are more example questions related to error handling:

  • If the API request fails, how many times should we retry? Should we only retry for specific status codes?

  • What error message should we display to the user if they hit [insert rare corner case here]?

  • Should I throw an exception if we hit [insert weird edge case here]?

If you’re unsure of how to proceed when a block of code may fail, ask your coworkers or the project stakeholder. They will often help clarify how you should handle errors in certain situations.

Business logic. The business logic encodes into your program the core set of business rules that determine how it should store, modify, and delete data. Many times, this logic will closely resemble real-life concepts such as inventories, invoices, accounts, or processes.

Making assumptions in business logic can lead to misunderstandings in how the business operates, so it’s always important to clarify any ambiguity before proceeding. A small bug in your business logic can have enormous downstream effects such as data loss, duplicate data, or many other unintended consequences.

exampleHere are a few example questions for clarifying business logic.

  • Should we start the free trial when the user creates their account or after they verify their email?

  • Should we send an alert as soon as the sensor temperature reaches the high heat threshold, or after it’s above the threshold for more than X seconds?

  • Should we disable the form after the user submits their answers, or should we allow multiple submissions?

If you don’t have all the information you need to complete a task, you’ll need to ask for clarification. It’s your responsibility to find gaps in the requirements and to ask questions that will clarify any ambiguities and fill in those gaps. The better you’re able to fill in the missing requirements, the better software you’ll write, and that starts with communicating clearly to others when you aren’t sure how to handle specific scenarios in your code.

Making Assumptions

Programmers enjoy autonomy in their role. When you’re starting out in your career, you’ll need some help when completing your tasks, and that’s okay. By the time you start a new task, a lot of the big decisions will already have been made for you, and your job will be to implement a predetermined solution. This is good because it allows you to focus on writing quality code rather than trying to implement a solution where the end result isn’t crystal clear.

When you are implementing a set of requirements someone else has already figured out, you can focus on good coding fundamentals and understanding how your changes fit into the larger context of the codebase. In a well-architected system, sometimes you’ll be able to add powerful new functionality by making a few small tweaks, and in doing so, you’ll see firsthand how good code should be written. You’ll pick up new ideas over time and form your own opinions based on what you’ve seen work well in the past. In the process, you’ll build confidence to make more decisions on your own in the future.

As you gain experience and grow into a seasoned developer, you’ll naturally want to make more decisions on your own rather than be told how to implement solutions. This is a good thing, but it can be a difficult time in your career to navigate. At some point, you’ll find yourself at a crossroads where you’re confident enough in your ability to devise a working solution, but your ideas may not be sufficient for what the senior engineers had in mind.

You’ll disagree on how some solutions should be implemented. These conflicts are difficult because emotions often get the best of people. Sometimes, you’ll be right, but other times, the senior engineers will rely on their experience to override your decisions. Sometimes, they will have legitimate reasons based on experience to push back on your decisions, but other times, it may come down to personal preference.

It’s easier said than done, but as a junior engineer you need to do your best to take your emotions out of the development process. If you can separate your decision-making process from how you view your coding ability, you’ll be able to navigate through your career better.

As you gain experience, you’ll encounter ambiguity in requirements that might seem insignificant in the context of the task, and it’s natural to want to make decisions on your own. You were hired because you’re smart, and your employer values your ability to make good decisions, so why wouldn’t you want to make certain decisions on your own? After all, Steve Jobs famously said that “it doesn’t make sense to hire smart people and tell them what to do; we hire smart people so they can tell us what to do.”

The longer you work as a software engineer, the more you’ll gain a better feeling for what to do when faced with ambiguity, but be careful in these situations. Making your own decisions can be rewarding, but if you’re not careful, you may be making decisions based on flawed assumptions.

An assumption is a prediction that something is true without proof or evidence. The thing that makes assumptions dangerous is that they’re often made based on experience, and as a junior engineer, you may not have enough experience to safely make certain assumptions. Senior engineers, on the other hand, may have relevant experience to contradict your assumptions, which often leads to conflict.

When a senior engineer pushes back on your solution, you may feel like you’re being personally attacked, but try to view their experience as an opportunity to learn why they’re pushing back. Most of the time, it’s as simple as asking why they think your assumption is wrong. You may be surprised that they mention some reason you hadn’t considered or some edge case that you didn’t think was possible.

The important thing to understand in these situations is that there are still a lot of things you don’t understand about software development. The same can be said about senior engineers as well, but it’s especially true for those who have only been working professionally for a few years. You may think you know quite a bit, but the reality is that you’ve only scratched the surface.

So how do you avoid these conflicts while you’re building up the confidence to make your own decisions? The answer is to make assumptions, but verify those assumptions before implementing any of your decisions. It’s a subtle detail, but it’s an important one. You can still make decisions on your own when the opportunity arises, but verify with your manager, a senior member on your team, or a project stakeholder before proceeding with any development work.

When you verify your assumptions before beginning any coding, you’ll avoid any situations where you’ll have to redo your work because you made a decision that wasn’t correct or that someone disagreed with. It’s faster and cheaper to refactor an idea than it is to refactor code, so it’s always good to double-check with others that your assumptions are correct and that you’re not missing some important context before implementing your decisions. If your assumptions are correct, that’s great! You’re getting smarter and your experience helped you come to the correct decision. If not, ask for clarification on why your assumptions were incorrect, or why your decisions won’t work in the given situation. And by coming to a conclusion before writing any code, you’ll be able to avoid conflicts after you’ve put in time and effort coding an incorrect solution. It’s a win-win for everyone involved, and in the worst case, you’ll walk away learning something new about why your decision was flawed and what you can do better next time.

Asking to Learn

At the beginning of this section, we discussed why children ask questions, and how it helps them connect ideas and form a better understanding of the world around them. In a way, most engineers also do this without even thinking about it. By asking the different types of questions you’ve learned about in this section—asking for help and asking for clarification—you’ll naturally learn things from the answers you’re given. You’ll learn why you should choose one software design over another, or why you should consider a certain edge case when you didn’t think it mattered.

In some cases though, you’ll just have curiosity for how something works or why something is the way it is. You won’t necessarily be asking for help or to clarify anything, but you’ll still have questions. In this case, you’re just asking a question for the sake of learning.

Sometimes it’s good to challenge the status quo and ask your teammates why something is the way that it is. In some cases, the answer may be underwhelming. You may receive an answer such as “well, that’s just how we’ve always done it.” In asking these questions, you’re challenging the other engineers to think through why they’ve always done it that way, and what they could do to improve a process or part of the codebase.

In other cases, there may be a very good reason for why something is done a certain way that you may not have realized. There may be historical context that you were unaware of because you joined the team after some important decisions were made. Perhaps you never would have known this had you not asked the question in the first place.

In some cases, asking questions will help you understand your team’s processes, which will help you be a better contributor in the future. Other times, asking questions will help you understand why a part of the system is architected a certain way, which will help you write better software in the future. Or there may be times where asking questions helps you understand someone else’s thought process and how they approach solving a problem. It can be eye-opening to see what considerations and trade-offs someone else takes into account when making decisions, and this can often influence your own thought processes in the future. Asking good questions helps to uncover new ideas, techniques, and processes that you can leverage to become a better software engineer.

Write It Down

When you read about a new idea for the first time or hear an explanation for why some status quo is the way that it is, it can be revealing. In some cases, it can change your perspective on a topic or how you view certain things. Over time, these new ideas may fade out of your memory, and you’ll soon forget about them.

It’s always helpful to have a good note-taking system in place to keep track of small task lists and meeting notes, and a good habit to get into is to document new ideas and concepts as you learn them. This can be in a personal note-taking app or your private space on your company’s knowledge platform.

First, writing things down helps you internalize new concepts and retain new ideas by forcing your brain to process the information in a more detailed way. It’s often said that the best way to learn something is to teach someone else, so try writing your notes in a way that teaches your future self what you want to remember. Not only will it help you internalize what you learn, but you’ll be able to review and reference your notes in the future.

Never Stop Learning

Software engineers are curious creatures. We strive for continuous improvement and are constantly looking to refine our understanding about how the world works around us. We apply years’ worth of experience to the code we write, but more importantly, we never stop learning from the decades’ worth of experience that our colleagues bring with them.

Asking questions is a fundamental aspect of our jobs as software engineers, and asking good questions is equally an art and a science. Over time, you’ll hone your ability to ask better and better questions, but it’s always important to keep in mind which kind of question you’re asking and the context in which you’re asking it. Sometimes you’ll get the answers you were looking for, sometimes you’ll get unexpected answers that change your perceptions, and sometimes you’ll get insufficient answers that will require additional questions. If there’s one thing for certain though, it’s that you should never stop asking questions, because asking why is fundamental to growing as a software engineer.

How to Read Unfamiliar Code28 minutes, 11 links

It’s a common misconception among students and aspiring programmers that professional software engineers spend all of their time writing new code and building new systems from scratch. Many new developers face a rude awakening when they land their first job and find out that this is far from the truth. In fact, aside from planning and documenting, most of your early-career time will be spent maintaining, extending, and fixing bugs in legacy codebases. You’ll be tasked with making small- to medium-sized changes to the code that your team members wrote, and you may sometimes find yourself working on code written by someone who is no longer with your company.

Working on legacy code gives you the opportunity to get experience working on a mature codebase. In a way, it can be seen as a rite of passage on some teams because it allows you to get familiar with complex abstractions and business logic. There will be design patterns, coding standards, and test cases that the previous programmers established and that you’ll be able to follow when making your changes. Following established patterns when learning a new codebase will help you focus on the behavior of your code without getting too bogged down in details about the design and architecture of the code.

This is especially true when you join a new team, because you’ll be learning the nuances of the codebase and the business rules while getting up to speed. Your manager will probably start you off with some small bug fixes and enhancements before you graduate to larger projects. In many cases, it would actually be counterproductive for you to jump in and make large changes to a codebase that you don’t understand very well. That would be very risky, especially as a junior software engineer still learning the best practices.

You’re reading a preview of an online book. Buy it now for lifetime access to expert knowledge, including future updates.
If you found this post worthwhile, please share!