Some software engineers have computer science degrees, others went to coding bootcamps, and others are self-taught. In my professional experience, software engineers with computer science degrees are in the minority, and the majority of professional software engineers are self-taught. Partly this is because computer science is a relatively young field of study compared to other STEM majors, and partly this is because of the boom in software engineering jobs over the last decade. There are more software engineering jobs than qualified candidates, and many engineers get their start in software engineering by getting hired on potential and then learning on the job. Unfortunately, more often than not it’s people who “fit the profile” of what an engineer looks like, as dictated by the media, who get that opportunity to learn on the job.
I believe in running equitable hiring processes that recruit from a representative candidate pool, evaluating candidates on objective measures and not just looking for people who “fit the profile.” I believe in hiring for both experience and potential, and supporting high potential software engineers in their career growth paths. This is some of the most important work engineering managers can do.
After an engineer joins our team, one key support structure I’ve found to be helpful is the Software Engineering Learning Plan.
What Is It?
The Software Engineering Learning Plan is a list of project-independent computer science basics relevant to the work your team does, like big-O notation, optimizing SQL queries, strategies for debugging.
This came out of a conversation I had with a software engineer on my team who had a degree from a top university, but not in computer science. She was concerned she was missing key fundamentals, but didn’t know what she was missing. I decided to try to answer this question. I have a degree in computer science from Carnegie Mellon University, and reviewed my college coursework to determine which topics from this program I used most often over the course of my career. I remember my degree program being an amazing and rigorous experience, a place where I proved to myself I could learn and build anything. But sadly not necessarily all of my coursework (like building artificially intelligent Connect-4 game players, or algorithmic music generation, or soccer playing robots) was relevant to the professional software engineering jobs I had held.
I looked over my notes from the classes I had taken, and determined that concepts and tools from two classes: Algorithm Design and Analysis, and Introduction to Computer Systems came up the most across a wide variety of software engineering jobs and industries. Skimming through the textbooks for these classes: Introduction to Algorithms and Computer Systems: A Programmer’s Perspective, I came up with a list of topics that I could easily cross reference with project work senior engineers on my team had comfortably covered in the last year, and next to each topic, the project or feature where the topic came up, and ideas for when this topic might come up in future project work. Over time, we’ve added topics — mostly related to system scalability — that senior software engineers encountered in the course of their careers and reported that they spent focused learning time on.
How To Implement It
The interested engineer and I went over this topic list together and identified opportunities to go deeper into these topics in the natural course of project work. Then we picked out 2 or 3 topics she’d look for opportunities to dig into over the next quarter. We made a spreadsheet together to track the selected skills, her current comfort level, interest level, learning plan, related upcoming tasks, and self-reported status digging into the new skills.
It’s important to emphasize that focusing on a few areas at a time is already going above and beyond, and that it wouldn’t be reasonable to ask a engineer to complete the whole list in a year (as this is a professional software engineering job, not a bootcamp or computer science degree program). But having a roadmap of concepts to learn about over the course of several years can be a helpful conceptual scaffold.
It’s also important to note that working on a Software Engineering Learning Plan doesn’t have to add a lot of extra time to the engineer’s week. Often opportunities to dig into topics like query optimization or learning about a web architecture patterns come up organically, and being intentional about which tasks to pick up or pair on can help junior engineers opportunistically grow their experience in efficient and rewarding ways.
The Software Engineering Learning Plan Template
Here’s a list of the possible topics we include in Software Engineering Learning Plans, spanning the realms of algorithms, coding, data, databases, devops, and web architecture. We build and run highly scaled web applications at MoveOn, so this set of topics encompasses most of our work. Working collaboratively with the engineer, we pick out a list of ~10 topics for each plan, and 2–3 per quarter to focus on.
Algorithms:
- Big-O notation: what is it? when is it useful to know about? what are some recent examples where we had to think in terms of big-O to solve an optimization problem?
- Big-O application: think in terms of Big-O to determine how long a chunk of code will take to execute
- Big-O application: think in terms of Big-O to determine how much system memory a chunk of code will use
- Recursion, differences between iterative and recusive solutions
- Hash functions
- Memoization and in-memory caching
Coding:
- Logical Operators
- Regular Expressions
- How to create a simple standalone script: running it, parsing user input, credential management
- Design patterns: what are they, why are they useful? Commonly used design patterns in our codebases.
- Design patterns: the Observer pattern, and examples in frontend frameworks for async updating
- Design patterns: use of the MVC, MVW, and MTV patterns in full-stack web frameworks for keeping code organized
- Commonly used data structures: hash tables / dictionaries, lists / arrays. when do we use each? what are some examples from our current systems? What are the Big-O differences between them for data storage and retrieval?
- How code uses system memory, and the scaling limits of system memory
Data:
- Deep dive into our actively used data models
- Differences between relational and document-oriented data models, and when to use each
- Data Modeling for new projects: how does this work, why is it important to do before starting a new project, and why do we start with the data model first when planning out new projects?
- Query optimization: what do you do when someone complains that their query is running slowly? What is explain-plan and when is it useful?
- What are some big-O notation strategies for thinking about query analysis?
Databases :
- MySQL vs Postgres: what’s the difference between these SQL dialects, and which databases use which in our system?
- The different databases we use in AWS: RDS MySQL, RDS Postgres, redis, redshift: what are the differences between them and when do we use each?
- What is the primary / secondary architectural pattern in databases in the RDS ecosystem (mysql, postgres), what is it useful for, how does this help us scale, and with which systems do we use this pattern?
- How do we think about debugging databases? What system properties, metrics, alarms do we look at and what do they tell us? (like db cpu, or number of connections)
DevOps:
- How to figure out what’s going on with this linux box? sshing in, finding and editing the crontab, finding and tailing logs, listing running processes, checking system properties like memory and disk space used
- Command line fu: bash, aliases, ssh key mgmt, keyboard shortcuts
- Deployment systems: how does code get from our desktops or github to production? Examples of different techniques used by different systems
Web Architecture:
- How do we think about debugging frontend code? What debuggers are used, and what information can they tell you about the running system?
- How do we think about debugging backend code? What debuggers are used, and what information can they tell you about the running system?
- Where is the line between frontend and backend in our systems? How do you know when code is executing on the frontend vs the backend?
- Synchronous vs asynchronous processing: what does it mean when we have code run synchronously in the context of a web server process vs async in some other worker process? When do we use task schedulers?
- Basics of system scaling: understanding the system resource usage and in particular the rate at which data is read and written for each category of incoming request
- Common scaling issues and how to address them: webservers running out of memory or CPU, databases running out of available connections, systems not keeping up with incoming request throughput
- How do requests get from your browser into our systems? What are all the steps, and systems a request passes through and in what order? Use debuggers to trace the path of a request from the browser all the way through one of our systems
- What are the components of the internet that a request passes through before it actually gets to the systems we manage? What is DNS? What is edge caching? Why are these systems important and how are they used?
- Load balancers: why are these important? How are they used and how do they help us scale?
- Queues: using them to smooth out write performance, how to integrate with other system components, when is it helpful to consider adding a queue into a server architecture?
- Caching: when do we use caching? What does it help with? What are some pitfalls to try and avoid?
Conclusion
Supporting high potential software engineers is some of the most important work engineering managers can do. Investing in not just projects but people makes your team stronger, and it makes the tech world more fair as it explicitly opens the opportunity to learn and grow on the job to everyone on your team, not just people who already happen to have access to supportive learning spaces.