Coding <pasta/> from scratch | TELUS
Skip to content
New Telus Digital Logo
Fabio, author of the blog, on his computer at the TELUS tower.
Fabio, author of the blog, on his computer at the TELUS tower.

Coding pasta from scratch

Development · Nov 28, 2019

If you ask a developer just what exactly coding is, there's a very good chance you'll hear a metaphor that has been used since the time of 8 bit computers running BASIC (yes, I'm old):

"Code is just like a recipe to make a dish or bake a cake. Once you write it, the computer will follow it to the letter".

While this statement is true, it only scratches the surface of how these things are alike. Below we'll go through the process of coding pasta from scratch!

NOTE: All code in this article is obviously pseudocode, but I've tried to make is as consistent as possible without sacrificing legibility for non-developers.

Making pasta the easy way

There are many pasta recipes out there, but the first decision you must make is how much effort you intend to put on making dinner. If you're hangry and in a hurry, the answer is probably "not much". But don't worry, we got you covered! You can rely on a huge ecosystem of ready-made components that can be integrated with minimal effort.

First we'll acquire the basic ingredients and ensure we have the necessary tooling ready to go:

import spaghetti from 'pasta'
import tomatoBasilSauce from 'can'
import water from 'faucet'
import { salt, pepper } from 'spice-rack'
import { stove, pot, saucePan, colander } from 'kitchen'

The next steps should be pretty familiar for anybody who's ever set foot in a kitchen.

async function cookSpaghetti() {
  // Fill pot with enough water. 
  // Our containers have a built-in add() method that 
  // takes ingredient and quantity.
  const potWithWater = pot.add(water, '3 litres')

  // Wait until water is boiling. This takes a while.
  // In JavaScript it would be an `async` function that 
  // we must `await` to proceed.
  // The second argument is the temperature - water boils at 100C.
  const potWithBoilingWater = await stove.heat(potWithWater, 100)

  // Next we add a bit of salt to the water.
  // Adding it before now would slow the boiling process! 
  // That's physics for you.
  potWithBoilingWater.add(salt, '1 Tbsp')

  // Next we add the spaghetti!
  // We're hungry, so we'll make the whole package.
  const spaghettiPot = potWithBoilingWater.add(spaghetti)

  // Now we'll cook it for the appropriate amount of time.
  // Our stove has a cook() function which takes a container 
  // (e.g a pot), heat intensity and cooking time in seconds.
  // We can find out the cooking time from the spaghetti 
  // package - convenient!
  const cookedSpaghetti = await stove.cook(
    spaghettiPot, 'medium-high', spaghetti.cookingTimeInSeconds)

  // All that's left is drying it and setting it aside
  return colander.dry(cookedSpaghetti)
}

Now we're cooking with gas! Unless you have an electric stove, but that's alright for our purposes.

Since our sauce comes from a can, preparing it is as easy as:

async function prepareSauce() {
  const panWithSauce = saucePan.add(tomatoBasilSauce)
  const cookingTime = 5 * 60 // 5 minutes = 5 * 60 seconds
  return stove.cook(panWithSauce, 'medium-low', cookingTime)
}

Now we'll put everything together, but before that: have you noticed we're using async and await? These keywords allow us to wait until certain time-consuming tasks are done. Without getting into much technical detail, async functions return something we call a promise. Think of a promise as a smart timer: it always rings as soon as a task is done.

We're using Promise.all() below, which lets us to make spaghetti and sauce at the same time. Once both are ready to go, our smart timer rings and we can finally eat!

async function makeSpaghetti() {
  // Your own robotic kitchen!
  // You can go watch some Youtube videos in the meantime.
  const [cookedSpaghetti, warmSauce] = 
    await Promise.all([cookSpaghetti(), prepareSauce()])

  return cookedSpaghetti + warmSauce
}

// Here we go!
const spaghettiBowl = await makeSpaghetti()
spaghettiBowl.add(pepper, '1 pinch') // Optional!

// Bon appetit!
spaghettiBowl.eat()

Voilà! A spaghetti bowl. This approach would be the equivalent of using readily available npm packages for pretty much everything.

This is usually good enough most of the time - especially when you're very hungry! - but sometimes you may wish to do things in a more customized way. Maybe you're inviting friends over and you want to impress, or maybe you have a secret sauce recipe that's been in the family for generations!

You may just want to have a better understanding of how your ingredients fit together so you can code (I mean, cook) better, which is an excellent reason to do it. Who needs an excuse to try new things? Not you! 🙌

Great, let's make some custom pasta then! The question is: how much effort do you want to put on it?

The laborious approach

The good news is we already have a relatively modular pasta making framework will well-defined interfaces, so the main process won't change much. To make things easier, we'll create new modules that will be imported in the exact same way as before:

// On our main file - usually called index.js
import spaghetti from './custom-spaghetti'
import tomatoBasilSauce from './custom-sauce'

This means we don't need to change the main code as long as our spaghetti and sauce behave the same way (and they should - consistency is very important!).

Our custom-spaghetti.js module would look something like this:

import flour from 'cupboard'
import largeEgg from 'fridge'
import { salt } from 'spice-rack'
import { bowl, fork, rollingPin, pastaMachine } from 'kitchen'

function makeDough() {
  // Our API is chainable, we're good like that
  const dough = bowl.add(flour, '2 cups').add(salt, '1/2 Tsp')

  // Mix dry ingredients with fork
  dough.mix(fork)

  // Add eggs
  dough.add(largeEgg, 3)

  // Mix until it actually becomes dough
  while(dough.state !== 'mixed') {
    dough.mix(fork)
  }

  // Now we knead until it has the right consistency.
  // There's no objective measure for that, 
  // so let's just say it should be 42.
  while(dough.consistency < 42) {
    dough.knead()
  }

  return dough
}

// This should be self-explanatory at this point
function spaghettify(dough) {
  const semiFlatDough = rollingPin.squish(dough)
  const paperThinDough = pastaMachine.flatten(semiFlatDough)
  return pastaMachine.cut(paperThinDough)
}

// The final step
function makeSpaghettiFromScratch() {
  const dough = makeDough()
  return spaghettify(dough)
}

// Here we define the output of our module. 
// Our code expects to receive not only the spaghetti, 
// but also information on how long we should cook it. 
// Fresh pasta cooks in no time, so we must reflect that!
const spaghetti = makeSpaghettiFromScratch()
const customSpaghetti = {
  ...spaghetti, cookingTimeInSeconds: 2.5 * 60 }

export default customSpaghetti

As in real life, there's much more effort involved. On the other hand, this is your pasta recipe, and you have much more control over the outcomes. The more you work on your pasta implementation, the more you optimize its performance according to your own KPIs. 😎

(By the way, this is an actual pasta recipe.)

Next, the sauce! Here's our custom-sauce.js file:

import peeledTomato from 'can'
import garlic from 'groceries'
import basil from 'backyard' // The freshest!
import { salt, pepper, oliveOil } from 'spice-rack'
import { 
  knife, 
  potatoMasher, 
  woodenSpoon, 
  saucePan, 
  stove 
} from 'kitchen'

async function makeSauce() {
  const mincedGarlic = knife.cut(garlic, 'fine')
  const sauceBase = saucePan.add(oliveOil, '3 Tbsp')
                            .add(mincedGarlic)
                            .add(salt, '2 Tsp')
                            .add(pepper, '1 Tsp')

  // Let's cook it for 3 minutes to flavour that olive oil
  const amazingSauceBase = 
    await stove.cook(sauceBase, 'medium', 3 * 60)

  // Now for the star of the show
  const rawTomatoSauce = amazingSauceBase.add(peeledTomato, 4)
  const tomatoSauce = 
    await stove.cook(rawTomatoSauce, 'medium', 5 * 60)

  // We'll get in and crush those tomatoes after 5 minutes...
  potatoMasher.mash(tomatoSauce)
  woodenSpoon.stir(tomatoSauce)

  // ...then we'll cook it some more
  const amazingTomatoSauce = 
    await stove.cook(tomatoSauce, 'medium-low', 10 * 60)

  // Finish it off with some fresh basil and we're done!
  return amazingTomatoSauce.add(basil, '8 leaves')
}

const tomatoBasilSauce = await makeSauce()

// Just like before
export default tomatoBasilSauce

(If you're wondering: yes, this is also a real recipe - my own! 😉)

Bon appetit

The beauty of code is that it's both a metaphor and an analogy for things that exist in real life. By definition, any process or thing that can be described can also be coded.

Likewise, code can be like a fast-paced novel or a long academic paper. Good developers strive to make their code easy to read - even compelling at times. It's a tool for communication among humans above everything else.

Code is intended to be read and understood. Computers will simply run it, no matter how legible it is. The old myth that "real programmers don’t write comments" must be put to rest for this very reason.

Due credit

I'm far from being the first person to write about how coding and cooking are alike. If you like the analogy, be sure to check Brett Terpstra's article "Kitchen Coding". It's a different take with less code and different insights into tooling and ecosystems in general.


We hope you enjoyed the recipes! Interested in joining our team? Check out our careers page and apply today.

Authored by:
Fabio Neves
Technical team lead
Web developer since the mid-90s, former instructor at Lighthouse Labs, trapeze artist and music producer.