- Lab
- Core Tech

Guided: Understanding Variables and Data Types in JavaScript
In this lab, without any prior knowledge of JavaScript, you’ll create a recipe ingredient converter from scratch where recipe data can be held and modified depending on the number of servings to make. Along the way, you’ll learn how to use variables of different data types, organize data with collections (Arrays, Sets, and Maps), and reuse code by writing your first functions.

Path Info
Table of Contents
-
Challenge
Introduction
This lab will guide your exploration of various data types and collections in JavaScript, assuming minimal prior programming experience. You'll see some of the pros and cons of representing data in different ways, and discover which way could be suitable for a particular use case: Creating a recipe ingredient converter. To cap off the lab, you'll write two functions as a first step toward code maintainability and coding efficiency.
Some notes:
- You'll do all your coding in the file
main.js
below the first line of code, which imports some utilities required for this lab. - If you get stuck on a task, you can consult the files within your
solutions
folder.
As you make changes, you'll want to see how they affect your program. To see your program's output at any time, type
node src/main.js
at the command prompt in the Terminal pane, and hit Enter. (For subsequent runs, you can simply press the up arrow to recall the previous command so you don't have to type it out again.) The output will appear in the terminal pane. If you try it now, before writing code as described in the tasks, there won't be any output yet. - You'll do all your coding in the file
-
Challenge
Primitive Variables
Variables and data types are fundamental to using JavaScript. This lab will get you comfortable with some of the most common ones.
A variable has a name and stores a value. That value may be simple, like a number, or it may be complicated, like a structure holding other variables. The simple ones are called primitive value types, and often the most useful ones are numbers, strings, and booleans.
Declaring variables
You can create (declare) variables using the keyword
var
followed by what you want to name your variable, like this:var myIngredient = "water";
Here, the
= "water"
provides an initial value formyIngredient
to hold, and that value is a string. JavaScript knows it's a string — and you're able to know this too — because the value is surrounded by quotation marks. (Apostrophes work too, but you need to use quotes to pass the checks in this lab.) The final semicolon (;
) simply ends the statement, like a period does in English.You can use
let
similarly:let myOtherIngredient = "lemon juice concentrate";
The biggest difference:
var
is function-scoped, visible throughout the innermost function containing the declarationlet
is block-scoped, visible only within the innermost pair of curly braces ({}
) containing the declaration
You'll learn more about the insides of functions and see some blocks later in this lab, but there are several other nuances between
var
andlet
that are too advanced to cover here. So for simplicity, in this lab you're expected to always uselet
, which is often the better choice anyway.Throughout this lab, you can choose variable names as you see fit, but their values must be as requested in order to pass this lab's tasks.
Calling functions
Functions consist of reusable code. They can be run multiple times, or once, or never — although, there's not much point in writing functions that are never used.
You run (or invoke or call) a function by putting
()
after its name. Some functions use parameters, which are values you can (or sometimes are required to) put inside the()
for the function to use.For example,
console.log
is a function, and calling it withconsole.log("hello");
will outputhello
to the terminal. Here, the code passes one parameter (the string"hello"
) toconsole.log
when calling it. (If you're wondering about the.
inconsole.log
, it's because thelog
function is being accessed as part of theconsole
object.)If a function takes multiple parameters, it's common that it will expect them in a particular order. So
myfunction(myIngredient, myQuantity)
won't work the same asmyfunction(myQuantity, myIngredient)
. Note how parameters are separated by commas.In this lab, you'll make use of a function called
templates.displayFirstIngredient
provided by the lab. It formats the data you pass to it and outputs it to the terminal. It takes two parameters: first, an ingredient name, and second, a quantity. ## About the provided functionDisplaying your data in
templates.displayFirstIngredient
works something like this:console.log(` My Recipe --------- Ingredients (for 1 serving): - ${ingredientQuantity} ${ingredientName} `);
- There is only one statement here, a
console.log
call. - In this case, there's a special, multiline string in between the
()
. The fact that it's surrounded with backticks (`
), rather than quotes ("
) or apostrophes ('
), allows it to be written cleanly over multiple lines. It also allows the incorporation of the values of variables by putting their names inside${}
.
See if you can map how it's written in code to how the output looks in the terminal.
-
Challenge
Arrays
You could continue with the same approach to declaring variables as your recipe grows. But you'll soon have a lot of similar variables — perhaps
ingredient1Name
,ingredient2Name
, and so on. And you'll have to change your output code every time you add an ingredient. That's not only inefficient, but also insufficient: In a real-world program, your users would add ingredients while the program is running (at run time), not while you're programming it.One solution for holding multiple variables of similar meaning — several ingredient names, for instance — is to use arrays. An array variable has one name and many values.
You create arrays by surrounding a comma-separated list of values with square brackets (
[]
):let myEmptyArray = []; let aSingleValuedArray = ["water"]; let thisArrayHasTwoValues = ["water", "lemon juice concentrate"]; let thisOneHasThree = [2, "three", 5.0]; ``` ## Limitations Note that one problem with this approach is that duplicate ingredients are allowed. For example, you (or in a fancier program, your users) could add `"water"` ten times to `ingredientNames` and `"1 cup"` ten times to `ingredientQuantities`. (Try it out!) It's a type of human error that a different structure could disallow, perhaps by raising an error if there are duplicates, or by automatically deduplicating. ## About the provided function `templates.displaySiloedArrays` is more complicated inside because it needs to handle arrays. It prepares a formatted list in a variable called `ingredientList`, which is later used in `console.log()`. Here's how the preparation looks:
let ingredientList = ''; for (let i = 0; i < ingredientNames.length; i++) { let ingredientName = ingredientNames[i]; let ingredientQuantity = ingredientQuantities[i]; ingredientList +=
- ${ingredientQuantity} ${ingredientName}\n
; }You know what `let ingredientList = '';` does, but the next line is more complicated. 1. First, `for` means to _loop_, or run the same code — whatever is between the curly braces (`{}`) — repeatedly. 2. The next three statements, together in parentheses, let us describe how the repetition works: 1. `let i = 0;` sets up a number, `i`, to keep track of how many iterations (repetitions) have been done. 2. `i < ingredientNames.length;` is the condition that's checked just before each repetition. 1. If it's a true statement, the code will run again. 2. If not, the `for` loop is done, and the program will continue running whatever code comes after the closing `}`. (In this case, the only thing after the `}` in the file is the `console.log()` statement.) 3. The last statement gets run after each iteration of the loop. In this case, `i++` increases the counter `i` by one. So when `ingredientNames` holds two values — i.e., `ingredientNames.length` is `2` — this means `i` starts off as `0`, the code loops, then `i` is `1`, it loops a second time, and then `i` is `2`. At this point, `i < ingredientNames.length` is false (because `2` is not less than `2`) so the loop ends, having run twice in total. What about the code in the `{}`? This is where the list is built: 1. `let ingredientName = ingredientNames[i];` gets a single value from `ingredientNames` and stores it in `ingredientName`. The values of `ingredientNames` are numbered (_indexed_), starting at `0`, and `[i]` indicates which value. For example, when `i` is `0`, the first time through the loop, `ingredientNames[i]` is `ingredientNames[0]` which is the first value, `"water"`. 1. Similarly, the corresponding value from `ingredientQuantities` is stored in `ingredientQuantity` in the next line. 1. `ingredientList +=` means to take the string value of `ingredientList` and append whatever comes next. In this case, it's `- ${ingredientQuantity} ${ingredientName}\n`, which can be found verbatim in the previous task, except for the fact that it ends with `\n`. In a string, `\n` is a special _escape sequence_ that gets interpreted as a newline character — the equivalent of pressing Enter. In other words, each time this code loops, it adds to `ingredientList` in the same format used for the single ingredient in the previous task: hyphen, space, quantity, space, name, newline. But it's done so it works within a loop using the values at index `i` in each of the two arrays.
-
Challenge
Sets
Arrays aren't the only type of variable that can hold multiple variables within them. Another option is the
Set
.Set
s and arrays come with different feature sets — neither is inherently superior — but arrays are used much more often in the real world. However, aSet
ignores duplicate values, which might help your users to avoid accidentally adding incorrect data.To create a
Set
, you use a constructor after the keywordnew
, like this:let myNiftySet = new Set();
Note the
()
afterSet
— they're not technically required when they're empty like this, but they are required in the form you'll see next.Set
s are so similar to arrays that the cleanest way of declaring aSet
is to give its constructor an array:let myNiftySet = new Set([1, 2, 3, 2]);
In this case, the duplicate
2
would be ignored, somyNiftySet
would contain only1
,2
, and3
. ## LimitationsReal recipes often have two ingredients with the same quantity (e.g., 1 cup of beans and 1 cup of rice). Unfortunately, having a
Set
just for quantities doesn't let you support this use case, because you can't store"1 cup"
twice in your quantitiesSet
. Automatic deduplication seemed like a good way to avoid a potential issue, but in this case, it created a real issue instead.Also, in this task and the previous one, you were asked to create two multi-value variables. You can think of them as silos, because they're independent: One variable holds all the names, the other holds all the quantities. This opens the door for bugs, though, because this siloed data can get out of sync.
For example, try the beans-and-rice recipe here and you'll see incorrect output, since your names
Set
would contain two values, and your quantitiesSet
would only contain one ("1 cup"
).But this can happen with siloed arrays, too: If you were to write some code to add or delete an ingredient, you might forget to modify both arrays in the same way, and end up with names and quantities that don't match.
About the provided function
The difference in
templates.displaySiloedSets
is that theSet
data is converted back to array format at the start of thefor
loop:let ingredientNames = Array.from(ingredientNamesSet); let ingredientQuantities = Array.from(ingredientQuantitiesSet);
It's not the only way to accomplish this, but because of the siloed data, this approach makes it easier to access the same index in both variables at the same time.
-
Challenge
Objects and Maps
To have the structure of your data match the recipe you're trying to represent, it makes more sense to use something that associates an ingredient name with a quantity. This way the structure itself goes a long way toward keeping names and quantities in sync.
In JavaScript, an object is one such structure. It's like a group of variables, but:
- The variable names are called keys.
- When providing initial key-value pairs during declaration, instead of
=
, you use:
. - You can change how many key-value pairs you have at run time.
Like a
Set
, duplicate keys are impossible in an object. Setting the value of a key a second time just overwrites its earlier value.Like an array, JavaScript can see that a value is an object just by the syntax. You can tell an array because it's surrounded by
[]
; likewise, you can tell an object by the{}
:let myNiftyObject = { myFirstKey: "my first value", mySecondKey: "my second value", };
(The comma after
"my second value"
is optional, but it's good form to include it, because you can add or remove lines without having to think about applying different rules to different lines within your{}
— they simply all end in a comma.)Careful with
[]
versus{}
, though —[]
also has a special, non-array meaning when used a certain way with an object. Namely, putting[]
just after an object variable name lets you get or set the value of a key, even if you only know the key at run time:let whichKey = "mySecondKey"; myNiftyObject[whichKey] = "my new second value";
If you do know the key before run time, you can also use
.
to access its associated value, but only if it doesn't have any spaces, e.g.myNiftyObject.myFirstKey
. If the key does have spaces, you can still use the[]
form from above, using a string instead of a variable, e.g.,myRecipe["lemon juice"]
. ## LimitationsNote that the order of the list is not necessarily always preserved in older JavaScript engines that don't implement at least the ECMAScript 2020 specification (ES2020) — but any engine currently in long-term support will work fine.
You may also encounter difficulty if you happen to need keys that are the same name as a built-in
Object
property, likeconstructor
. Workarounds for this and other limitations are beyond the scope of this lab, though.About the provided function
The code in
templates.displayIngredientsObject
is simpler than in the previous two tasks, showing how object syntax can be nice to work with. The new lines were:for (let ingredientName in ingredients) { let ingredientQuantity = ingredients[ingredientName];
Here you can see a different type of
for
loop, particular to objects. It usesfor...in
syntax. In each loop iteration, JavaScript setsingredientName
to one of the keys ofingredients
.Then the first statement in the loop itself gets the associated value using the
[]
syntax discussed above, and setsingredientQuantity
to that value. After that, everything is prepared to use the same formatting code as previous tasks to build the list.Map
s vs. objectsMap
s and objects are conceptually similar, but their syntax and features differ. To change your approach to aMap
that associates ingredient names with quantities, you'll use a syntax that may seem like a combination of yourSet
and object solutions. You might expect to be able to convert an object to aMap
in a similar way to how you converted an array to aSet
, withnew Map({
followed by your key-value pairs followed by})
. Thenew Map()
part is correct, but despite the similarity betweenMap
and object, you can't convert an object directly this way.It may seem odd, but to provide an initial value to convert into a
Map
, you can use an array of arrays like this:let myNiftyMap = new Map([ ["first map key", "value for first map key"], ["second map key", "value for second map key"], ]); ``` ## Limitations Beware that the object ways of getting and setting values (e.g., `myRecipe.water = "1 cup"` and `myRecipe["lemon juice concentrate"] = "1.5 tbsp"`) will _seem_ to work with `Map` to some extent, but that doing so uses `Map` incorrectly and breaks the functionality of standard `Map` functions (like `get`, `size`, and `has`). This is fairly insidious, so it's worth paying special attention to syntax when working with `Map`s. ## About the provided function While objects are more common than `Map`s, the code in `templates.displayIngredientsSimpleMap` is even simpler than it was with the object solution. There's just one new line, taking the place of two lines in the previous task:
for (let [ingredientName, ingredientQuantity] of ingredients) {
First, note that this uses yet another type of `for` loop. With an object you can use `for...in` syntax, but with a `Map`, you use `for...of`. The other difference is that this line uses _destructuring_. JavaScript hands us each key-value pair in the `Map` as a two-value array, just like the ones you used to construct the `Map` in the first place in your solution. The nice part of the syntax here is that by writing `[ingredientName, ingredientQuantity]`, JavaScript takes that two-value array and assigns its first value to a variable called `ingredientName` and its second value to one called `ingredientQuantity`, for each iteration of the loop. Like this, the exact same variables are available for the list-building code, so the code to append to `ingredientList` remains as it was in all the previous tasks. ## A better structure for quantities In all the previous tasks, the way you've represented quantities — as a string like `"1 cup"` — means that they can't be easily multiplied because they're strings and not numbers. To address this, you'll need to improve your data structure by representing the two parts of each quantity (the number and the unit of measurement) separately, using an object having two keys, `quantity` and `unitOfMeasurement`, like this:
{ quantity: 1, unitOfMeasurement: "cup(s)"}
You'll use an object like this as the value in each key-value pair of your `Map`, as a drop-in replacement for the simpler string values like `"1 cup"` you used previously. Please note: 1. Earlier, you were told that you can choose variable names as you see fit. In this case, `quantity` and `unitOfMeasurement` are _not_ variable names — they're keys, and therefore they're data, and therefore they must be spelled and ordered as shown in order to pass the remaining task checks. 1. Since your ultimate goal is to be able to multiply the quantities, the units of measurement have ambiguous plurality (`"cup(s)"`) so that they'll make grammatical sense no matter what the number part of the quantity is. (Also note that "tbsp" is a valid plural already, i.e., "1 tbsp" and "2 tbsp" are correct, so no "(s)" is needed.) ## About the provided function `templates.displayIngredientsMap` is a bit more complicated to accommodate your new data structure, but it doesn't use any syntax you haven't seen earlier:
for (let [ingredientName, ingredientQuantityObject] of ingredients) { let ingredientQuantity =
${ingredientQuantityObject.quantity} ${ingredientQuantityObject.unitOfMeasurement}
;The `for...of` syntax for `Map`s is used again, but this time, the value in the key-value pair is an object. The first statement within the loop then uses that object's data to reconstruct a quantity string in the format you've seen in all the previous steps so that the rest of the display code can remain the same. Another way this could be done is to destructure both the array and the object within it, by naming the keys the object will have:
for (let [ingredientName, {quantity, unitOfMeasurement}] of ingredients) { ingredientList +=
- ${quantity} ${unitOfMeasurement} ${ingredientName}\n
;
} -
Challenge
Functions
Now that you have a more sophisticated data structure in place, you're ready to add code to multiply your recipe quantities.
But mind the capitalization: You're about to encounter a
set
that's completely unrelated to theSet
data type you saw earlier. In this case,set
is a function you'll call on your ingredients variable (i.e.,ingredients.set()
, assumingingredients
is the name of yourMap
variable) to provide new values for it.You can write
ingredients.set
becauseingredients
, like allMap
s, comes with theset
function, just likeconsole
has itslog
function. Usingingredients.set()
, you can associate a new object with a given ingredient name.Map.set
takes two parameters: first, the name of the key, and second, the value to set it to. For example, if you wanted to triple only the quantity of water in the recipe, you could write:ingredients.set("water", { quantity: ingredients.get("water").quantity * 3, unitOfMeasurement: ingredients.get("water").unitOfMeasurement, });
You can see that
ingredients.get("water")
is called twice here. TheMap.get
function returns a value (something that neitherconsole.log
norMap.set
do). This means that the expressioningredients.get("water")
gets replaced with a value — in this case,{ quantity: 1, unitOfMeasurement: "cup(s)"}
, which is the object associated with the"water"
key. From there, the code above retrieves the values associated in the returned object using.quantity
and.unitOfMeasurement
.So that's how you would associate a new value with just the
"water"
key. But you'll want to work on all key-value pairs to triple the whole recipe. ## A more direct wayFor this task, you were told to use
Map.set
and create a whole new ingredient quantity object to associate with each key. You can accomplish the same goal, but instead triplingquantity
in place, with this simple line:ingredientQuantityObject.quantity *= 3;
Wait, weren't you warned earlier about using this form (
.key =
) withMap
s? Yes, and the line above does not stray from that advice. That's because it's not setting the value associated with aMap
key (e.g.ingredientName
), it's setting the value associated with an object key (quantity
).However, using
Map.set
is closer to what's needed for the next task, so the approach in this task has prepared you to more easily build on your solution.About the provided function
You use the same function, but pass it
3
as a second parameter — it handles this by changing the output from1 serving
to3 servings
. Thes
is added on the end using logic that checks whether the number of servings equals1
or not.(It's a more sophisticated way of dealing with plurals than appending nouns with
(s)
like you did with"cup(s)"
, but it's beyond the scope of this lab to explain the inner workings here. The reason it's not used for the ingredients themselves is that it becomes quite a bit more complicated if you don't know the noun beforehand: As you saw withcup(s)
andtbsp
, it's not always appropriate to add ans
. Other classes of examples includebox(es)
andox(en)
.)Reusing code
The
for
loop from your previous task can be turned into generic, reusable code, by putting it inside a function.In general, a function lets you take more specific code and reuse it. Take this code, for example:
let five = 5; let tripleFive = five * 3;
The act of tripling a number can be generalized, so that you're not limited to tripling
five
but can triple any given number. The "given number" simply becomes a parameter of a function, and the result is the function's return value:function triple(numberToTriple) { return numberToTriple * 3; }
Note that the function took the original, overly specific logic (
five * 3
) and made it work more generally asnumberToTriple * 3
. The function then returns the value by having it immediately follow thereturn
keyword.Also, note that the code isn't run until the function is called. Defining the function, as above, simply prepares it to be run.
However, once the function is defined, you can call it as many times (and with as many different numbers) as you like, rather than having to copy and paste code to reuse its logic:
let tripleFive = triple(5); let tripleSeven = triple(7); let tripleEleven = triple(11); let tripleUserProvidedNumber = triple(userProvidedNumber);
In the case of your project, creating a function will prepare your code for reuse in the final task.
The function you need to make will take two parameters (how many servings and a
Map
of ingredients) and it will return an object containing the serving count and a newMap
,ingredientsForXServings
, with appropriately multiplied quantities:function multipliedRecipe(servingCount, ingredientsForOneServing) { return { servingCount, ingredientsForXServings }; }
You might have noticed two new things:
- The object you're returning is anonymous. You know it's an object because of the
{}
, but there's no variable declared to hold it. It's ad-hoc. - There's no
:
or value withservingCount
. This is shorthand forservingCount: servingCount
, i.e., you're saying the object should have a key called"servingCount"
and its associated value should be the value of theservingCount
parameter that was passed to the function. - The same goes for
ingredientsForXServings
, except that the value won't come from a function parameter, it will come from a variable you'll have to declare within the function.
Now, all that's missing from the function you'll create is the declaration of
ingredientsForXServings
and the code to populate it with the correct data, by turning yourfor
loop into generic, reusable code. ## About the provided functiontemplates.displayRecipeObject
is quite similar to before. In fact, it simply calls the previous task's function, passing it the data it finds in the recipe object. It's the same as writing this:templates.displayIngredientsMap(recipe.ingredientsForXServings, recipe.servingCount);
templates.displayRecipeObject
is used identically in the next task.Combining lines
In the task above, you called
multipliedRecipe
and stored the result in a variable calledrecipeForThree
. Then you passed this variable todisplayRecipe
. But these two steps can be combined. In the next task, you'll skip this storage step, and just use the result of yourmultipliedRecipe
call as the value you pass directly todisplayRecipe
:templates.displayRecipeObject(multipliedRecipe(servingCount, ingredients));
This will be done inside a wrapper function — one that conveniently wraps up the functionality of the multiplying and displaying functions. The recipe data will also be wrapped up inside the function.
- The object you're returning is anonymous. You know it's an object because of the
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.