How to Make Assumptions (When Learning to Program)

Ethan Weiner
Launch School
Published in
6 min readDec 3, 2021


Humans make assumptions all the time. We assume that the nutrition facts on our cereal box are accurate. We assume that if we select two-day shipping, our package will arrive in two days. We assume that our fellow humans are real, biological creatures — not humanoid robots. If we didn’t make some assumptions, life would be pretty strange. Almost everything involves some element of uncertainty, so we make assumptions. Even if you make an A** out of U and ME — sometimes you just have to assume things.

Photo by Maximalfocus on Unsplash

But as a software engineer, assumptions can be dangerous. What if you assume a method is non-destructive, and proceed to use it on a data structure which you didn’t intend to mutate? Depending on the size and scope of the data structure, the implications could range from a minor upset to a lost job. So how do you navigate assumptions as a programmer?

Importance of Assumptions

Assumptions are an important part of life, and they are an important part of learning to program too. To illustrate this, let’s look at the basics of the mastery-based learning process that is at the heart and soul of Launch School.

Say you need to learn concepts A and B. First, you learn and master A and B in isolation. You might make some assumptions when learning A or B. In the next learning stage, you put A and B together. In this stage, you might uncover some things about A and B you didn’t know or understand before. Perhaps, some of your assumptions about A and B are broken.

Would you have been better off knowing all the technically “correct” details about A and B during the first learning stage? I’d argue not. Here’s why:

  • You can’t learn everything at once. You need to gain a solid, fundamental understanding of A and B before moving on to the next stage. This process might require making some assumptions for the sake of progressing forward.
  • Avoiding all the details gives you the breathing room to develop a valuable framework to think about a concept.

To see what I mean, let’s walk through a common assumption Ruby beginners make. During RB101, strings are introduced as collections of characters. This makes sense, and at first glance, it appears to be the case in Ruby:

  • We can access characters using the String#[] method
  • We can assign characters using the String#[]= method
  • Many string methods share names with, and have similar implementations to Array methods: #[], #[]=, #size, #slice, #count, #<<, etc.
Photo by Jason Leung on Unsplash

It is both reasonable and helpful to assume that Strings are collections. We can now feel comfortable performing collection-based operations on strings, such as element reference, element assignment, iteration, transformation, and reversal.

How to Properly Make Assumptions: The Mental Model

At this point, we might go ahead and make the following statement: “A string in Ruby is a collection of characters.” That’s fine, but we don’t yet know if that statement is technically accurate. Thinking of strings as collections is certainly useful, but is it correct?

A possible solution to this quandary is to utilize a mental model. Let’s rephrase our statement to “a collection of characters is a valid mental model for strings in Ruby.” Now, we’re free to write as many diagrams and as many notes as we need to reinforce that mental model, all while framing it as just that — a model. It might be wrong from a technical standpoint, but at the very least, it exhibits some parallels to what is right and serves as a stepping stone in the learning process.

The Assumption is Broken

A few more lessons in, and we encounter an exercise that breaks our mental model.

The problem: Write a method that capitalizes the first character of the original string.

The problem statement implies that we must mutate the original string. Here’s a potential solution in Ruby:

The first character of “launch” wasn’t capitalized. According to our original mental model, this should work. If a string is a collection of characters, we should be able to directly access the characters (so-called elements) in a string. But when we “access” the first character, and try to directly mutate it with String#upcase!, string remains unchanged. When an assumption like this is broken, a good first instinct might be to experiment:

The operation works as expected on an array.



Interesting. Even though the outer-scope variable string and the local parameter string refer to the same object (per Ruby’s parameter passing behavior), trying to retrieve the character at index 0 returns two different objects.

Let’s hop into irb:

irb(main):001:0> string = "launch"
=> "launch"
irb(main):002:0> string[0].object_id
=> 260
irb(main):003:0> string[0].object_id
=> 280
irb(main):004:0> string[0].object_id
=> 300

If strings in Ruby technically were collections of characters, we could access the exact object (character) located at any position in a string, and that object would be the same upon every access. But our experimentation shows us that we are returning a different object upon each element reference. If we read up in the documentation, we will soon find that String#[] returns a new substring from its caller — it doesn’t return any part of the caller itself. In fact, there is no way to directly access the character objects in a string in Ruby.

The behavior of String#[] is but one way that strings don’t behave like typical collections should. If we continued to dive deeper, we’d find that strings in Ruby are singular objects, not collections.

Overcoming the Assumption and Expanding the Mental Model

While subtle, this violates our mental model of strings as collections. But do we have to scrap that model? We could. But our model is still valuable. It is a useful way to think about a concept, and that way of thinking might provide benefits moving forward. Additionally, it might be technically accurate in other contexts. In another language, strings might actually be collections of characters.

Even if we do scrap the model entirely, our initial process of developing and refining can be a critical part of the learning process. Sometimes, we need to become fluent in one model to move on to another, and by initially abstracting away some of the details, we can focus on the details most relevant at the time, instead of getting bogged down in too many intricacies.

And since it’s just a model, we can still think of strings as collections of characters, even though we now know that’s technically inaccurate. This feels like a contradiction, but it’s a duality that should be embraced. The key is distinguishing between what is a mental model and what is technically accurate. I imagine this to be one of the reasons students and instructors at Launch School so frequently use the term “mental model”.

Finally, I will introduce an acronym to help you form and apply mental models (in software development, and maybe even in life): PCF

  1. Position: Establish a position (mental model).
  2. Clarity: Gain clarity over that position, and understand it deeply.
  3. Flexibility: Maintain the flexibility to update or replace that position as needed.

When we think about having a clear position, we often neglect the flexibility component. But I’d argue that those components can coexist.

Photo by Chris Lawton on Unsplash


Inevitably, you will make assumptions when learning to program. And you should make assumptions. But assume in the right way. Programming is often seen as a rigid discipline, but we should be cautious with fixed ideas. Mental models allow us to be cautious and flexible. That said, you don’t have to make a mental model about humans being real biological creatures. An assumption is good enough for that (I hope).

Note: Along with some of my own experimentation, a conversation from Ryan and Pete in the Launch School forum inspired me to challenge the assumption of strings as collections of characters:



Ethan Weiner
Launch School

Full stack software engineer - interested in learning, web development, and psychology