DEV Community

Cover image for The most useful things I got from my CS degree
I changed this as a joke and forgot
I changed this as a joke and forgot

Posted on

The most useful things I got from my CS degree

Introduction

I’m about 9 years into my career as a software engineer and while things like finite automata and suffix trees haven’t been very relevant, there were things I learned in my CS program that have been invaluable. I want to share those things with you so that you can learn those lessons in closer to five minutes than five years. At the end of this article, you’ll have everything you need to back-fill the most useful bits of a CS degree.

A framework for understanding performance

The most useful thing I got from my CS degree was a framework for understanding performance. I’ve found it really hard to discuss performance without at least implicitly using the language of Computational Complexity Theory (aka “Asymptotic notation” or “Big-O notation”). This tool gives us a way to answer questions like, “Is this too slow?” and “Will this approach cause performance problems in the future?”. The particular insight it provides is that we have to think of performance as relative to something. Often this is relative to the “size of the input”.

This is common when we think about the performance of database queries. If a query is doing a full table scan, then how long that takes is going to be directly (“linearly”) related to the number of rows. Similarly, “N+1” queries are problematic because instead of doing a fixed number of queries (which is independent of the number of rows) we’re doing a query for every row of data we want to return.

This also comes up on the front-end, especially in React. For example, if we need to do something computationally expensive we might want to avoid doing it every render and instead compute it once and store it somewhere. Or we might want to transform the data high up in the component hierarchy rather than having every row of the table component do its own transformation.

A third way this comes up is helping us understand when we can’t do any better than we already are. For example, if you’re trying to calculate something about all the payments made in your system over all time, at some point you need to examine every payment. Less commonly you’ll run into a problem that sounds straightforward but turns out to be “really hard.” You may have heard the term NP Complete before. In short, it means that something is fundamentally expensive to compute. My team ran into one such problem recently when thinking about how to efficiently pack UI components into a fixed amount of space.

A toolkit for improving performance

Thankfully, my classes also covered some ideas and techniques for speeding things up. For example, what should we do if a database query is doing a full table scan and that’s too slow? Add an index, right? But what is that actually doing? You may have noticed “btree” when examining a database or reading migration definitions. Btree stands for a data structure called a binary search tree and it makes it meaningfully faster to find things! (Search is O(log(n)) instead of O(n)). However, maintaining a binary search tree means updating it every time a new row is inserted, which slows down the inserts. It also takes up space since we need to maintain this data structure in addition to the existing table.

Binary trees and the idea of binary search are widely applicable. I use binary search to think about how I debug code when I’m unsure of where the problem is. It’s also the principle that shapes how git bisect works, which is a super handy tool.

Other useful “solutions” tools are using hashes instead of arrays in code, since it’s faster to look things up in a hash than an array. In jargon, lookups are constant time (O(1)) instead of linear time (O(n)). The trade-off, like with an index, is it takes up more memory. Memoization, and it’s inverse, dynamic programming, also show up but in our day-to-days we tend to think of these techniques simply as “caching”.

A surface understanding of memory and memory management

Another topic that I learned in school that comes up a lot is understanding how a computer stores things in memory. Most commonly, this helps me understand that weird things can happen when different parts of the code are pointing at the same object in memory. Some examples are:

  • The same symbol in ruby always has the same object id, which means it is referencing the same spot in memory. The same string, however, wont have the same object id.
  • If you use an array or dictionary as a default argument in python, it is only allocated once. That means that repeated calls to the same method will share a reference to the default argument, which can lead to very surprising results!
  • In python, strings are immutable, so if you’re building up a string in a loop with +=, it’s allocating a new string every time which can be slow!
  • In javascript, you can run into surprises with mutating arrays and objects. For example, two arrays with the same content won't be equal because they are different objects. This can cause react components to re-render even if you don’t want them to. Similarly, if you mutate an array by adding an entry to it, that won’t trigger a re-render in a React component because even though the contents are different, the object id is the same.

Another memory related thing that comes up is understanding that different data types take up different amounts of space. This is most visible when deciding what column type you want in the database. I encountered this when trying to increase the maximum size of an integer in a Rails validation to 30 million dollars (3 billion cents). The column for that attribute was a 32 bit integer so it couldn’t fit! We had to migrate the column type.

An appreciation for user-centered design

The user experience class I took was transformational for me in a few ways. Most importantly, it emphasized that there is (more-or-less) no such thing as user error. If a user is making a mistake using your software, it’s because you haven’t given them the necessary information, framing, or tools to help them succeed.

Other things that I remember from that class are:

  • Aesthetics matter. While it is "just what it looks like" there's research showing that people are better at using interfaces that are more appealing to look at.
  • Be intentional about what it says on your buttons. No one likes an alert dialog that says “Are you sure you want to cancel?” and then the buttons are “Cancel” and “Okay”.
  • Paper prototypes and clickable mockups are really helpful for building an intuition for how your users think about your tools.
  • Saturated blues are really hard on the eyes.

What I didn’t learn from my CS degree

All these things have been useful, but they didn’t teach me the things that I’ve found most valuable in my career.

Curiosity

Any knowledge gap can be filled by curiosity. Digging deep into the why of things, especially if you ran into something particularly surprising, can help you build knowledge incredibly rapidly. Investigating how things work beyond what’s required to fix a problem (sometimes even going to a package’s source code!) can provide you with building-block knowledge that allows you to learn other things more quickly.

Empathy

Whether it’s empathy for your users or your colleagues, it is a critical skill for building great products. It will lead you to the highest impact opportunities at your job, be that identifying frustrating interactions for your users, developing processes to help with cross team collaboration, or supporting a colleague who is struggling.

Future-minded-ness

Anticipating how code will evolve and building in such a way that allows for that evolution is as much art as science. I’ve made mistakes burdening a code base with future proofing that never got used. I’ve also jumped into projects too fast and shipped brittle solutions that couldn’t be changed without a bunch of work. Someday I’ll write a blog post about this…

When to ask for help

While there’s certainly value in powering through a problem, it can be a slow way to learn if it doesn’t build on existing knowledge. If you’re finding yourself lost or frustrated it can be fruitful to get some guidance from someone more experienced in the domain. This might be a colleague, a (former) classmate, a mentor, or someone else. They can often steer you towards the information you need and away from other information that, while useful, maybe be a distraction from the problem at hand.

In case it’s helpful, here’s a template I use when asking for help.

Resources

In case any of this sparked your interest or curiosity, below are some resources for learning some of these topics. If you’re familiar with others, let me know so I can add them!

Top comments (0)