Continuing the journey of thinking systematically

By Chris Meah 10 min read

The Importance of Systematic Thinking

When faced with a complex problem, it can be overwhelming to tackle it all at once. However, by breaking down the problem into its simplest forms and solving each component individually, we can make progress and eventually solve the bigger problem.

On the School of Code, in week 1 we focus on computational thinking - the art of breaking down problems to allow us to build up solutions. Lets refresh our memory on parts of that.

Breaking Down the Problem

The first step is to break down the problem into smaller, more manageable parts. By identifying the key components or sub-problems, we can focus our attention on solving each one separately. This allows us to gain a deeper understanding of the problem and find effective solutions. But most of all, small isolated problems are much easier to solve than large complex problems. The assumption is we can break apart a large problem, solve the sub-problems, and then tie those small solutions together to solve the large problem - and it works for 99.9% of problems!

Solving Each Component

Once we have identified the individual components, we can apply our problem-solving skills to address each one. By solving each component independently, we can build confidence and momentum. It’s important to approach each component systematically, using logical reasoning and available resources.

Abstracting Away

As we solve the smaller components, we can start to abstract away the details and focus on the bigger picture. Abstraction involves identifying common patterns or principles that can be applied to multiple components or even different problems. By abstracting away, we can develop general solutions that can be applied to similar problems in the future.

Climbing and Solving Bigger Problems

By systematically breaking down the problem, solving each component, and abstracting away, we can climb the ladder of complexity and tackle bigger problems. Each solved component becomes a building block for the next level of problem-solving. With practice, we can develop a mindset of abstraction and systematic thinking that allows us to approach even the most challenging problems with confidence.

Remember, solving complex problems takes time and effort. Embrace the process of breaking down, solving, and abstracting away. With each step, you’ll become a more effective problem solver and gain the ability to tackle bigger and more complex challenges.

Example

Task: Define a function that writes a quote object to file

This is an example of a first try taken from one of the task submissions. The team who submitted have this as part of a wider plan, and they have broken down what they want to do into comments, which is great!

export async function addQuote(quoteText, author = 'Unknown') {
  const JSONQuoteList = await fs.readFile('quotes.json');
  // Convert JSON to an array
  const quotes = JSON.parse(JSONQuoteList);
  // Add new quoteText and author to the list
  quotes.push(quoteText, author);
  // Convert the quotes data back to JSON
  const JSONData = JSON.stringify(quotes);
  // use the fs writeFile method to write the JSON data to file
  await fs.writeFile('quotes.json', JSONData);
  // return the new quote
  return (quoteText, author);
}

There’s a few things that would cause bugs here. Can you spot them?

If you can, great. But imagine you couldn’t… what would you do? How would you move forwards? We might be comfortable with the notion of computational thinking, and getting to grips with applying it, but this is the typical daily scenario where the power of computational thinking really comes into its own if you embrace it and practice. Let’s delve in together.

First of all, if we have no idea why it’s not working how we’d like, what should we do?

BREAK IT DOWN! BREAK IT DOWN!

That’s right, of course! We break it down. But what does that mean? We’ve already planned. We’ve already said on each line what we want… aren’t we done?

Right again. So, we’ve starting breaking down from our main objective to steps along the way. That’s great. Now we’re going to level up and think about how we can break down the complexity of our current task to take the simplest possible next step to put us onto sure footing.

Reducing the complexity could be something like this…

At the moment we are inside an async function trying to return the new entry of a list read in from a file as a JSON and then parsed into an array with that entry added to it and then the whole list string-ified into a JSON and written back to the file based on the parameters handed into the function (phew! 😅). That is a lot of cognitive load.

Our job as programmers is to break down the complex to the simple, and from the simple build up to solve the complex.

Let’s see that as a stack of things that could go wrong, or as our alter ego (which we will be embodying for this part) Sherlock Holmes would think, a list of suspects. Sherlock, or any good detective, would systematically check off the suspects.

Even Sherlock might break computers sometimes, but he’d figure out the problem too (we hope)

Cognitive load stack (suspect list):

  • async function
  • function parameters
  • returning from a function
  • reading from a file
  • writing to a file
  • parsing a JSON
  • encoding a JSON
  • adding to an array

Let’s tackle that last one first; adding to an array. What we should do is isolate that concept, and make sure we know it’s working. Trying to do that inside the function we constructed would be complex as all the other concepts and code written could influence it, and we wouldn’t if things weren’t working because of that part or any other. We’re in a tangled spaghetti web of contexts that we’d need to painstakingly unpick to get working… let’s simplify things and tackle on strand of spaghetti at a time.

So, outside of any context in a nice isolated sandbox (maybe just in my JS console, or a different file or function - I’ll do the console for now as it’s the simplest way to get started), let’s test we can add items to an array.

const array = [];
array.push('quoteText', 'author');
console.log(array); // result: ['quoteText', 'author']

We created the simplest scenario that isolates the concept we’re wrestling with. Create an empty array, add to it, and see what the array looks like. In our case, our array has 2 items now; the “quoteText” and “author”… Ah! We didn’t specify what we wanted… let’s say we want an object containing the quoteText and author. In that case:

const array = [];
array.push({ quoteText: 'quoteText', author: 'author' });
console.log(array); // result: [{quoteText: "quoteText", author: "author"}]

Nice, we were able to add an object to the array. We can tick that off our list.

Cognitive load stack:

  • async function
  • function parameters
  • returning from a function
  • reading from a file
  • writing to a file
  • parsing a JSON
  • encoding a JSON
  • adding to an array

Let’s now take another item off the stack. The JSON and file stuff all seems under the same umbrella of “working with files”, so let’s leave those for now and take on the returning from a function.

function returnFrom() {
  return ('quoteText', 'author');
}

console.log(returnFrom()); // result: "author"

Ah-ha! Another suspect stepping out of line. We wanted, surely, both the quoteText and author returned, not just “author”. JavaScript is returning the last expression evaluated in the line after return it turns out… weird. Maybe we could return an object instead containing the information we want.

function returnFrom() {
  return { quoteText: 'quoteText', author: 'author' };
}

console.log(returnFrom()); // result: {quoteText: "quoteText", author: "author"}

Another one bites the dust.

Cognitive load stack:

  • async function
  • function parameters
  • returning from a function
  • reading from a file
  • writing to a file
  • parsing a JSON
  • encoding a JSON
  • adding to an array

We’ve solved them in isolation, knowing that we can now abstract away from the code and just say “add to the array” having confidence what we have is working. Let’s combine these to lego bricks we’ve created into a larger structure containing the 2 concepts.

function addQuote() {
  const array = [];
  array.push({ quoteText: 'quoteText', author: 'author' });
  return array;
}

console.log(addQuote()); // result: [{quoteText: "quoteText", author: "author"}]

Code Concepts, Assemble! 🦸‍♂️

Nice and easy, combining the 2 worked. Let’s tackle another item off our stack, function parameters.

function addQuote(quoteText, author = 'Unknown') {
  const array = [];
  array.push({ quoteText, author }); // making use of the object property shorthand: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer>
  return array;
}

console.log(addQuote('Hello', 'Bob')); // result: [{quoteText: "Hello", author: "Bob"}]

Easy-peasy this stuff… 🍋

Cognitive load stack:

  • async function
  • function parameters
  • returning from a function
  • reading from a file
  • writing to a file
  • parsing a JSON
  • encoding a JSON
  • adding to an array

Let’s tackle the async function…

async function addQuote(quoteText, author = 'Unknown') {
  const array = [];
  array.push({ quoteText, author });
  return array;
}

console.log(addQuote('Hello', 'Bob')); // result: Promise

Ah - a Promise… and…

Energetic Audience Chant: You always await a Promise!

async function addQuote(quoteText, author = 'Unknown') {
  const array = [];
  array.push({ quoteText, author });
  return array;
}

console.log(await addQuote('Hello', 'Bob')); // result: [{quoteText: 'Hello', author: 'Bob'}]

Kerrrching 💰

Cognitive load stack:

  • async function
  • function parameters
  • returning from a function
  • reading from a file
  • writing to a file
  • parsing a JSON
  • encoding a JSON
  • adding to an array

We’re layering in simple concepts one at a time, systematically ticking them off knowing in full confidence they are working how we want them to. If at any point we get unexpected behaviour, we can break apart and inspect the code aspects 1 by 1, suspect by suspect. We’ll be able to move forwards and get to a place where we either solve the problem of understand fully what the problem isn’t (valuable when you’re knee-deep in a complex project) and know how to ask for help more effectively.

Let’s tackle the last parts now ⚽️

With reading from a file we want the array not to be empty, but to come from a file…

🤔

In fact, I’ll let you take it from here. The important thing is not the code. It is that system of breaking down the problem, solving the individual parts, and building back up so that you can abstract away and solve more complex problems with your rock solid lego block creation 🧱

Let us know in the comments if this is useful/not, unclear/clear, makes sense/doesn’t.

Computational thinking takes a long, long, long time to really get to grips with and master - you are way ahead of the curve here, and by embracing this and using this spaced repetition and each challenge as a way of practicing the skill, you’ll rocket ahead of the rest of the world of wannabe problem solvers in no-time 🚀

✌️