generated from nhcarrigan/template
feat: add post about thinking algorithmically
All checks were successful
Node.js CI / Lint and Test (push) Successful in 45s
All checks were successful
Node.js CI / Lint and Test (push) Successful in 45s
This commit is contained in:
235
posts/how-to-think-algorithmically.md
Normal file
235
posts/how-to-think-algorithmically.md
Normal file
@@ -0,0 +1,235 @@
|
||||
---
|
||||
title: "How to Think Algorithmically"
|
||||
date: "2025-12-10"
|
||||
summary: "A structured approach to programmatic problem solving."
|
||||
---
|
||||
|
||||
If you have ever watched me help someone debug their code in a Discord server, you have probably noticed something: I almost never start by looking at their code. Instead, I ask them to explain their logic. What are they trying to accomplish? How do they think it should work?
|
||||
|
||||
This is not because I am lazy or trying to make things difficult. It is because I have learned, through years of teaching and debugging, that **most coding problems are not actually coding problems - they are logic problems**.
|
||||
|
||||
## The Problem with Starting with Code
|
||||
|
||||
When you sit down at your keyboard and immediately start typing, you are trying to solve two problems at once: figuring out what you want to do, and figuring out how to express that in code. This is like trying to learn a new language while simultaneously trying to write a novel in it. You are juggling syntax and logic, and it is incredibly easy to drop one or both.
|
||||
|
||||
Consider this scenario: you are trying to write a FizzBuzz algorithm. If you jump straight into code, you might write something like:
|
||||
|
||||
```javascript
|
||||
for (let i = 1; i <= 100; i++) {
|
||||
if (i % 3 === 0) {
|
||||
console.log("Fizz");
|
||||
}
|
||||
if (i % 5 === 0) {
|
||||
console.log("Buzz");
|
||||
}
|
||||
// ... wait, what about FizzBuzz?
|
||||
}
|
||||
```
|
||||
|
||||
You have already hit a problem, and you have not even finished writing the code yet. But if you had worked out the logic first, you would have caught this issue before you ever touched your keyboard.
|
||||
|
||||
## The Algorithmic Thinking Approach
|
||||
|
||||
Instead of starting with code, start with plain English. Write out your instructions step-by-step, as if you were explaining to a very literal, very stupid toddler who will do exactly what you say and nothing more.
|
||||
|
||||
Let us use FizzBuzz as an example. Here is how I would break it down:
|
||||
|
||||
1. First, I look at the number.
|
||||
2. Is the number divisible by three?
|
||||
3. If it is, I say "Fizz"!
|
||||
4. Is the number divisible by five?
|
||||
5. If it is, I say "Buzz"!
|
||||
6. Is the number divisible by three AND five?
|
||||
7. If it is, I say "FizzBuzz"!
|
||||
8. If I have said nothing at all, I say the number.
|
||||
|
||||
Now, here is the crucial part: **I test this logic manually before I write any code**. What happens if the number is 3? What should happen? What about 2? 5? 7? 15?
|
||||
|
||||
1. First, I look at the number. The number is 3.
|
||||
2. Is the number divisible by three? Yes, 3 is divisible by three.
|
||||
3. If it is, I say "Fizz"! Okay, "Fizz"
|
||||
|
||||
Saying a word ends my logical flow. I've said what I need to say, so I'm done.
|
||||
|
||||
Since we are expected to say "Fizz" when we see 3, this makes sense. What about 5?
|
||||
|
||||
1. First, I look at the number. The number is 5.
|
||||
2. Is the number divisible by three? No, 5 is not divisible by three.
|
||||
3. If it is, I say "Fizz"! It is not, so I do not say anything.
|
||||
4. Is the number divisible by five? Yes, 5 is divisible by five.
|
||||
5. If it is, I say "Buzz"! Okay, "Buzz".
|
||||
|
||||
Again, our logic looks correct. When I see 5, I *should* say "Buzz". What about 7?
|
||||
|
||||
1. First, I look at the number. The number is 7.
|
||||
2. Is the number divisible by three? No, 7 is not divisible by three.
|
||||
3. If it is, I say "Fizz"! It is not, so I do not say anything.
|
||||
4. Is the number divisible by five? No, 7 is not divisible by five.
|
||||
5. If it is, I say "Buzz"! It is not, so I do not say anything.
|
||||
6. Is the number divisible by three AND five? No, 7 is not divisible by three or five.
|
||||
7. If it is, I say "FizzBuzz"! It is not, so I do not say anything.
|
||||
8. If I have said nothing at all, I say the number. Okay, 2.
|
||||
|
||||
Yep, that all looks right. When I see 2, I *should* say 2. What about 15?
|
||||
|
||||
1. First, I look at the number. The number is 15.
|
||||
2. Is the number divisible by three? Yes, 15 is divisible by three.
|
||||
3. If it is, I say "Fizz"! Okay, "Fizz"
|
||||
|
||||
WAIT! That's an issue! When I see 15, I *should* say "FizzBuzz". Oh no!
|
||||
|
||||
Because saying something ends my logic, I need to check for three AND five before I check them individually - the "three and five" condition can never be reached.
|
||||
|
||||
So I fix my logic:
|
||||
|
||||
1. First, I look at the number.
|
||||
2. Is the number divisible by three AND five?
|
||||
3. If it is, I say "FizzBuzz"!
|
||||
4. Otherwise, is the number divisible by three?
|
||||
5. If it is, I say "Fizz"!
|
||||
6. Otherwise, is the number divisible by five?
|
||||
7. If it is, I say "Buzz"!
|
||||
8. Otherwise, I say the number.
|
||||
|
||||
Now I test again: 3? "Fizz" ✓. 5? "Buzz" ✓. 15? "FizzBuzz" ✓. 2? 2 ✓.
|
||||
|
||||
**ONCE THE LOGIC IS SOUND AND ALL OF MY MANUAL TESTS WORK, THEN I WRITE CODE.**
|
||||
|
||||
This approach serves two critical purposes:
|
||||
|
||||
1. I figure out the logic independently from the code, so I am not juggling both logic and syntax.
|
||||
2. If my app does not work, I can look for an error in the code because I know the logic is sound (since I tested that before writing any code).
|
||||
|
||||
## The Importance of Precision
|
||||
|
||||
Computers are like children who will do only and exactly what you say. They cannot infer your intent. They cannot read between the lines. They will follow your instructions to the letter, even when those instructions lead to nonsense.
|
||||
|
||||
I learned this lesson recently while helping someone solve a pyramid-building problem. They were trying to build a pyramid out of a character, with a specified number of rows. Their initial instructions were something like:
|
||||
|
||||
1. Look at the string.
|
||||
2. Look at the integer.
|
||||
3. Write the string.
|
||||
4. Go to the next line.
|
||||
5. Write the same string from before again.
|
||||
6. Add two more of the same single string beside it.
|
||||
7. Repeat until you have the same number of rows as the integer.
|
||||
|
||||
When I followed these instructions precisely, I got:
|
||||
|
||||
```
|
||||
x
|
||||
xxx
|
||||
xxxxx
|
||||
xxxxxxx
|
||||
xxxxxxxxx
|
||||
```
|
||||
|
||||
But that is not a pyramid! A pyramid should be centered, like:
|
||||
|
||||
```
|
||||
x
|
||||
xxx
|
||||
xxxxx
|
||||
xxxxxxx
|
||||
xxxxxxxxx
|
||||
```
|
||||
|
||||
The instructions were missing a crucial detail: the spacing. But more importantly, they were not precise enough. "Add two more of the same single string beside it" - beside what? Where? The instructions assumed I would understand the intent, but a computer (or a very literal person following instructions) would not.
|
||||
|
||||
After several iterations, we refined the instructions to be precise:
|
||||
|
||||
1. Look at the string. It is what we want the pyramid to be built by.
|
||||
2. Look at the integer. It is the amount of rows we want.
|
||||
3. Add a number of spaces equal to the integer, and then write the string.
|
||||
4. Go to the next line.
|
||||
5. Write the same spaces string from before again, but get rid of one space, and write the string.
|
||||
6. Add two more of the same single string beside it.
|
||||
7. Write the whole space and string again from the last row, get rid of one space again, and add two more of the same string.
|
||||
8. Repeat step 7 until you have the same number of rows as the integer.
|
||||
|
||||
Now, when I follow these instructions precisely, I get the correct pyramid. The logic is sound. **Only then** do we start thinking about how to translate this into code.
|
||||
|
||||
## Translating Logic to Code
|
||||
|
||||
Once your logic is sound and tested, translating it to code becomes much simpler. You are no longer trying to figure out what to do - you already know that. You are just figuring out how to express it.
|
||||
|
||||
Let us go back to the pyramid example. We have our logic:
|
||||
|
||||
1. Look at the string and integer.
|
||||
2. For each row, calculate the number of spaces and the number of characters.
|
||||
3. Print the spaces, then the characters.
|
||||
4. Move to the next row.
|
||||
|
||||
Now we can think about the code structure:
|
||||
|
||||
- We need a loop to iterate through rows.
|
||||
- For each row, we need to calculate spaces (which decrease as rows increase).
|
||||
- For each row, we need to calculate characters (which increase as rows increase).
|
||||
- We need to print the combination.
|
||||
|
||||
The code almost writes itself once the logic is clear:
|
||||
|
||||
```javascript
|
||||
function pyramid(characterToBuildPyramidWith, numOfRows) {
|
||||
for (let row = 0; row < numOfRows; row++) {
|
||||
const spaces = " ".repeat(numOfRows - row - 1);
|
||||
const characters = characterToBuildPyramidWith.repeat(row * 2 + 1);
|
||||
console.log(spaces + characters);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
But notice: I did not write this code until I had worked through the logic step-by-step. I knew exactly what I needed to calculate and why, because I had already tested the logic manually.
|
||||
|
||||
## When Logic Breaks During Coding
|
||||
|
||||
Here is a scenario that happens all the time: you have worked out your logic, tested it manually, and it looks perfect. You start translating it to code, and halfway through, you realize something does not make sense. Maybe you cannot figure out how to express a particular step. Maybe you discover an edge case you did not consider. Maybe the logic just... does not work the way you thought it would.
|
||||
|
||||
**Stop coding immediately.**
|
||||
|
||||
I know, I know. Your instinct is to keep typing, to try to fix it in code, to hack around the problem. But resist that urge. If your logic needs revision, that is a logic problem, not a code problem. And logic problems should be solved on paper, not in your editor.
|
||||
|
||||
Step away from your keyboard. Go back to your plain English instructions. Walk through them again manually. Where does it break? What did you miss? What assumption did you make that turned out to be wrong?
|
||||
|
||||
Let us say you are working on that pyramid problem, and while coding you realize: "Wait, how do I know how many spaces to remove each time? Do I start with the full number of spaces, or one less?"
|
||||
|
||||
Instead of trying to figure this out in code, go back to your logic:
|
||||
|
||||
1. Add a number of spaces equal to the integer, and then write the string.
|
||||
2. Go to the next line.
|
||||
3. Write the same spaces string from before again, but get rid of one space, and write the string.
|
||||
|
||||
Test it manually: if the integer is 5, I start with 5 spaces. Then I go to the next line and have 4 spaces. Then 3 spaces. Then 2 spaces. Then 1 space. Then 0 spaces. That makes sense!
|
||||
|
||||
Now you can go back to your code with clarity. You know that for row 0, you need `numOfRows - 0 - 1` spaces (which is 4 for 5 rows). For row 1, you need `numOfRows - 1 - 1` spaces (which is 3). The pattern is clear because you worked it out in logic first.
|
||||
|
||||
The key principle here is: **if you discover a logic problem while coding, you have not discovered a coding problem. You have discovered that your logic was incomplete.** Fix the logic first, test it manually, and then return to the code.
|
||||
|
||||
This might feel inefficient. You might think "I am so close, let me just fix this one thing in code." But I promise you: fixing logic in code is like trying to repair the foundation of a house while you are painting the walls. It will not work, and you will make a bigger mess.
|
||||
|
||||
## The Benefits of This Approach
|
||||
|
||||
When you think algorithmically first, you gain several advantages:
|
||||
|
||||
**You catch logic errors early.** Instead of debugging why your code does not work, you debug why your logic does not work - and logic is much easier to debug than code.
|
||||
|
||||
**You separate concerns.** Logic and syntax are two different problems. Solve them separately, and you will solve them more effectively.
|
||||
|
||||
**You build confidence.** When your code does not work, you know it is a syntax issue, not a logic issue. That narrows down your debugging significantly.
|
||||
|
||||
**You communicate better.** When you can explain your logic in plain English, you can explain it to teammates, ask for help more effectively, and document your code more clearly.
|
||||
|
||||
**You learn faster.** By focusing on logic first, you develop stronger problem-solving skills that transfer across languages and technologies.
|
||||
|
||||
## The Takeaway
|
||||
|
||||
The next time you sit down to solve a coding problem, resist the urge to immediately start typing. Instead:
|
||||
|
||||
1. Write out your logic in plain English, step-by-step.
|
||||
2. Test that logic manually with several examples.
|
||||
3. Refine your logic until it works perfectly.
|
||||
4. **Only then** translate it into code.
|
||||
|
||||
You will find that your code is cleaner, your bugs are fewer, and your problem-solving skills are stronger. And hey - if you can explain your logic to me in plain English and it works, then translating it to code is just a matter of syntax. And syntax is the easy part!
|
||||
|
||||
> 💡 Remember: computers are really stupid toddlers who will do only and exactly what you say. Be precise, be thorough, and test your logic before you write your code.
|
||||
Reference in New Issue
Block a user