Transitioning out of the "beginner" mindset

MP 51: Habits and practices for taking your work seriously as a programmer.

In programming, as in many fields, we speak of a number of different levels of knowledge and experience. We use labels like beginner, intermediate, and expert. These categories are somewhat helpful, because they make it easier to find appropriate resources when we’re trying to learn something new. But they’re also somewhat arbitrary, so it’s good to recognize that there are no clear boundaries between the different categories. For example you can remain a “beginner” in some areas of programming while developing expertise in other areas.

Many people tend to think of themselves as beginners much longer than necessary. Sometimes this happens because people aren’t quite sure what to do after learning the basics of a programming language. Once you’ve learned the basics, there are a number of things you can do to get out of the beginner mindset, and into the mindset of a professional programmer.

I recently had the chance to speak with Michael Kennedy on the Talk Python podcast about this topic. The title of that episode was 10 Tips and Ideas for the Beginner to Expert Python Journey. This post is a reflection on that same topic. Some of the ideas described here are a little different than what’s discussed in the podcast, so if this is a topic that interests you please consider listening to that episode.

Here are the main suggestions:

  • Know your goals.
  • Have a project in mind.
  • Don’t limit your learning to what’s needed for your project.
  • Read good code.
  • Know your tools.
  • Learn how to test your code.
  • Know when a project is good enough.
  • Embrace refactoring.
  • Learn how to profile your code.
  • Write things down.
  • Go meet people.

Note: This isn’t a “countdown” style list. The most import ideas are discussed first, because they serve as a foundation for the ideas that follow.

Know your goals

Knowing your goals as a programmer is important because they can impact your choice of what to focus on. Consider questions like these when sorting through your motivations for learning to program, or learning Python in particular:

  • Are you trying to earn money?
  • Are you curious about programming?
  • Are you looking for an intellectual challenge?
  • Are you trying to automate a task or process?
  • Has all the hype about AI made you curious to see how it works?

Someone who’s trying to get a job or change careers will focus on different projects and topics than someone who’s just curious about programming. None of these goals are better than any others, and most people are motivated by a mix of these goals. Recognize your own goals, and share them with people who are helping you learn. They’ll have a better idea of what direction to steer you in, and what kinds of connections to help you make as well.

Have a project in mind

Some people get interested in programming because they have a specific project they want to complete. These people tend to be quite focused, because whenever they learn a new concept they immediately think about how they could apply that concept to their project. They’re not just passively receiving information, they’re always evaluating the usefulness of new concepts in a specific set of problems.

Almost everything in programming makes more sense if you can think about it in a specific context. If you don’t already have a project in mind, take some time to list out a few small projects that interest you. You might be interested in making your own text editor, building a website, or analyzing weather data for your area. Keep a small notebook or notes file, and jot down ideas for new projects whenever they come to you. They don’t have to be original ideas; reimplementing apps and projects you find interesting or useful is a great way to apply what you’re learning to real-world projects. Having a project in mind is also a good way to learn a specific framework or library.

Screenshot of GitHub repository page for educator_news.
About ten years ago I built a site called Educator News, a Hacker News clone for educators. It didn’t go very far, but I learned a lot about Django and managing user data from working on this project.

If you have a larger, complex project that you’re trying to build, consider identifying a few smaller projects to work on at times. It’s good to have a project to focus on, but if your project is too big you won’t get the experience of finishing something that you started. It can also be easier to learn new concepts and libraries in the context of simpler projects, where you don’t have as many different things to think about.

Don’t limit your learning to what’s needed for your project

While it’s good to have a project to apply new concepts to, you shouldn’t limit what you’re learning to what’s needed for that project. Let your project guide your learning, but always be ready to expand your toolbox and your understanding. You’ll come back to your main project with a fresh perspective, and you’ll find ways to apply what you’ve learned when new problems come up in your work.

Continuing to learn a variety of topics beyond your project’s immediate needs will also help you avoid getting stuck in one niche. If you decide to move on from your original project, you’ll be able to take on a wider variety of new projects.

Read good code

When you’re relatively new to programming, tying to read the code in well-established projects can be intimidating. When you look at the code in popular Python libraries, you can usually recognize that you’re looking at Python code. But it can also look quite different than the code you’ve written if you haven’t worked on a large and heavily-used project before. If you want to write code that works reliably and evolves steadily over time, it’s helpful to look at code that does exactly that.

These days, there are many ways to start looking at the source code of popular libraries. You can browse the code on GitHub. If you’re using virtual environments, look in the site-packages/ directory. There you’ll see all the libraries you installed with pip. You can open the .py files from these libraries in your text editor, and browse through them just as you would your own files. This is also a great way to start contributing to these projects. If you look at enough library code, you’re almost certainly going to start finding things you understand. At some point you’ll probably start finding things you can expand on and improve, especially if it’s a library you use often.

Finder window showing the contents of .venv/lib/python3.11/site-packages/. It shows folders named numpy, pandas, pip, plotly, pytz, setuptools, and tzdata.
In a virtual environment, the libraries you install are typically stored in a folder called site-packages/. You can look in this folder to start reading the code for any Python library you install to the virtual environment. You can also modify this code to make your own changes to these libraries.

Pay closer attention to tracebacks when they appear. Tracebacks show you the .py files that were being run when your program crashed. More importantly, they show you specific line numbers that were being executed just before the crash. Reading these lines, and the code around them, is a great way to build a better understanding of what’s happening when your code interacts with the libraries you’ve installed. Following tracebacks will also pull you into the Python standard library, which is well worth digging into.

The code base of the Python standard library is huge, but you don’t have to read a lot of it to start getting a decent understanding of how it’s organized, and how it works. Pick a module that you use often, that was introduced in a more recent version of Python. These modules tend to be written using modern Python practices and styles; some of the older modules were written before the community had settled on common conventions and practices. I’m trying to make time to read through the pathlib module whenever I have a question about how to do something with paths, or if I run into a bug when using pathlib. Reading the actual code in the module has led me to understand some things better than if I had only read the documentation.

Know your tools

One of the things I appreciate about programming as a field is that we all have access to really good tools. Don’t take this for granted; other fields don’t always have this luxury. There are many fields where professional-quality tools are prohibitively expensive to license and use on your own.

Programmers use many tools on a regular basis: text editors and IDEs, linters, debuggers, version control systems, code hosting sites, project planning and management tools, communication tools, and more. Be careful not to hop back and forth between too many tools in any one of these categories. For example it doesn’t really matter which IDE or text editor you use: VS Code, PyCharm, Sublime Text, Vim, Emacs, Geany, etc. Pick one of these, and learn it well.

Learn the keyboard shortcuts for the tools you use most often, and read the manual or documentation for your tools. Read a few articles about how others use them. When you know one tool well and you have some curiosity to satisfy, spend a little time with a different tool in order to see what might be missing from the one you usually use.

The tools we use affect the way we think. Learning to use the more powerful features of a tool can often help you think more powerfully about code as well.

The tools we use affect the way we think. Learning to use the more powerful features of a tool can often help you think more powerfully about code as well. On the other hand, some tools end up with too many features. Don’t be afraid to turn off or uninstall features that aren’t helpful to you, and only end up cluttering your workspace.

Learn how to test your code

I didn’t learn to test my code until I was well into my career as a programmer. I thought testing was a really complicated topic, and always put off learning about it. When I finally decided to run through a tutorial about testing, I remember thinking “That’s it?!” Testing large, complex projects is fairly complicated. But starting to test your code is probably less complicated than some of the code you’ve already written.

Testing your code has so many benefits. Here are just a few:

  • You won’t see the same bug twice. Whenever you fix a bug, you can write a test that makes sure your code handles the situation that caused that bug correctly. As long as you always run your tests against new versions of your project and make sure they pass, you won’t see the same bug a second time.
  • You can refactor your code without fear of causing bugs. If you’ve written tests for the behaviors that are already implemented for your project, you can freely change the code in your project without fear of breaking existing behavior. You can run your tests after making any changes you want, and if they pass then your project still works in the ways it was already working.
  • You can add new features without breaking existing behavior. This is similar to the previous point. Untested projects sometimes implement feature freezes, for fear of breaking existing behavior. This is unnecessary with a reasonable test suite. You can implement new features, run your tests to ensure that all existing features still work, and release the new features to your users.

I would argue that you’ll also be a little more relaxed in your work when you know how to test your code. Almost everyone wants the projects they work on to continue working when they step away from their computer. Stepping away from a project after ensuring that tests are still passing feels much different than stepping away from a project that doesn’t have any tests. This holds whether you’re stepping away for a lunch break, for the day, or for an indefinite period. Whenever you return to the project, you can quickly run the test suite and know if anything has broken since you last worked on it.

Know when a project is good enough

So many projects will never see the light of day because the person building them never defined what “good enough” meant for the project. You can always improve a project, which means you can always decide it’s not ready for anyone else to use just yet. Programmers are especially good at finding small, internal improvements to focus on instead of releasing a project.

When you start a project, take a few minutes to jot down your criteria for “good enough”.

When you start a project, take a few minutes to jot down your criteria for “good enough”. Releasing a project is scary, because maybe no one will use it—or maybe a whole bunch of people will use it, which can be just as scary. Making your “good enough” list early on is an effective way to make sure you will release your project at some point.

I often make an issue on GitHub repositories called something like Road to 1.0, along with a second issue that’s just as important: Post 1.0 tasks. When new ideas inevitably come up, I try to be careful about classifying each new idea or task as something that must be implemented before the project is released, or something that can wait until after the project has actual users. You can take a similar approach with adding significant new features to a project that already has end users.

You’ll never write perfect code, because no one ever does. Recognize when your projects are good enough, and ship them or make a decision to move on.

Embrace refactoring

The criteria for “good enough” is often situational. When a project or feature proves it’s worth, the criteria for good enough usually changes. You might need to make your code more efficient to handle an increasing user base, or more generalized to support a wider variety of usage patterns. Learn how to refactor your code, so you can engage in continuous improvement and manage technical debt reasonably.

There are formal and informal approaches to refactoring. Almost everyone engages in informal refactoring, finding ways to improve their code as they work. Formal refactoring methods ensure that all existing behavior is preserved as you restructure your code.

Regardless of your approach to refactoring, make sure you are using a version control system such as Git, and write tests for your code as well. With version control and tests, you’ll know immediately if any of your refactoring attempts break your project. When that happens you can easily roll back to the most recent working version of your project.

Learn how to profile your code

Profiling your code tells you which sections of your project runs slowly, and which sections run efficiently. In many cases, the slowest parts of a code base aren’t intuitively obvious. There are many factors that can affect how quickly or slowly a section of code runs. Without profiling, you can spend a lot of time and effort refactoring, only to find that your newly-refactored code doesn’t run meaningfully faster than the code you started with. If the refactored code is more maintainable that’s probably helpful. But if your goal was to improve your project’s performance you’ll probably be disappointed.

Profiling saves you this frustration by telling you exactly what parts of your code base to focus on. Accurate profiling will also give you a sense of how much benefit you can expect to gain from a specific refactoring effort. (For more about profiling Python code, see MP #10.)

Write things down

As a writer, it’s easy for me to encourage people to write things down. But it’s hard to be a good programmer these days if you don’t make a habit of writing critical information down in a timely manner.

There are a number of things you should consider writing in addition to the code you’re working on:

  • Write meaningful comments. People like to claim that good code speaks for itself, but that’s not entirely true. Write comments that explain why code is written the way it is. Also, the docstrings you write will show up in help() output and auto-generated documentation. Feel free to be brief, but write clearly about how to use the code that you write.
  • Write documentation. Your documentation doesn’t need to be as thorough and polished as the documentation we’re used to reading from the most popular Python libraries. But you should write documentation that covers the most common use cases for your projects. If the user base for your project grows, this core documentation can always be polished and expanded as needed. Good documentation will make other people more willing to collaborate with you as well.
  • Write a changelog. If your project doesn’t already have one, consider starting a changelog. Even in projects that don’t need a formal changelog, I’ve found that keeping one can be really helpful as the project evolves.
  • Communicate clearly with colleagues. Identify the communication platform your team or colleagues use, and write clearly about your work on that platform. Make sure your communication makes other people’s work easier, not harder. Focus on the information they need in order to collaborate effectively with you, and leave out irrelevant details that shouldn’t really matter to others.
  • Consider writing for an external audience. If you’re working on something interesting, considering writing an article, or starting a blog or newsletter. Even if you don’t think your work will be interesting to others, starting to write about your work will almost certainly become a beneficial habit that does lead to interesting writeups.
  • Consider keeping an ideas journal. Once you become comfortable with programming, you’ll have all sorts of ideas for projects you can build. If you don’t write these ideas down somewhere, most of them will be lost. Consider starting a paper or digital journal, even if it’s just a bunch of bullet points. The next time you’re looking for something to work on, this kind of list can be really helpful. A single idea in a journal like this can sometimes lead to life-changing projects.

Go meet people

There’s an old adage that still holds true for many people:

I came for the language, I stayed for the community.

Many people start to use Python because it meets their practical needs in a programming language. Once they’re using the language, however, people often tend to notice that there’s something special about the Python community. Most of the people behind the language have made an effort to be welcoming toward people who are writing their first lines of code, experts in the field, and everyone in between. Over time, that has led to a diverse and engaging community.

These days there are PyCons all over the world, and many conferences dedicated to specific Python frameworks as well. When you have the chance, consider attending one near you. You’ll almost certainly meet people whose ideas resonate with yours, and quite possibly make some lasting friendships as well. If you don’t think you can afford to attend, consider applying for a scholarship. Many Python conferences offer financial support to people who would otherwise be unable to attend, and many prominent people in the community got their start by taking advantage of these kinds of opportunities. This is how I was able to attend my first Python conference, and that experience changed the course of my life.

Many of these conferences also offer hybrid online attendance, and some are online-only. If you can’t attend an in-person conference, consider signing up for an online conference.


While categories like beginner, intermediate, and expert are helpful at times, they can also hold people back. It’s certainly good to recognize when you’re just starting to learn a language, but don’t let yourself get stuck in a beginner mindset too long.

If you can share any specific resources or experiences that helped you move out of the beginner mindset, please share them in a comment or a reply to this email. I’d love to hear concrete examples of what made people no longer feel like a beginner in Python, or what made you start to recognize that you’d developed expertise in a certain area of programming.