tutorial // Oct 01, 2021
How to Recursively Traverse an Object with JavaScript
How to write a function that looks for a specific key/value pair on an object and call that function recursively to traverse objects of an arbitrary depth.

Getting Started
For this tutorial, we're going to create a simple, single file Node.js project. On your computer, choose a good location for your file (e.g., a projects folder) and create a file called index.js
.
Next, make sure that you've installed Node.js on your computer. While the code we write will not depend on Node.js to work, we'll need it to run or execute the code we write inside of index.js
.
Once you have your file created and Node.js installed, we're ready to get started.
Creating a function to match objects by key and value
An easy way to understand the concept of recursion is to think of a spiral staircase in a house. In order to go from the top of the staircase to the bottom, you need to walk down one step at a time.
Though you do it automatically, technically you have a "function" in your brain that tells you how to walk down one step at a time until you reach the bottom. You call that "function" for each step in the staircase until there are no more steps. As you walk down, you tell the "function" to call itself again if there's a step after the current one.
This is how recursion works in JavaScript (or any programming language). You write a function that performs a task and have that function call itself again if it hasn't met some requirement—for example, finding a nested value or reaching the end of a list.
For this tutorial, we're going to write a function that focuses on the former: finding a nested object. More specifically, we want to write a recursive function that finds a nested object containing a specific key with a specific value.
First, let's create our base function and explain what it's up to:
/index.js
const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
// We'll implement our function here...
};
Our function will take three arguments: an object
to traverse, a keyToMatch
within that object, and a valueToMatch
within that object.
/index.js
const isObject = (value) => {
return !!(value && typeof value === "object" && !Array.isArray(value));
};
const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
if (isObject(object)) {
// We'll work on finding our nested object here...
}
return null;
};
Next, in order to avoid any runtime errors, in the body of our findNestedObject
function, we add an if
statement with a call to a new function we've added above isObject()
, passing in the object
argument that was passed to findNestedObject
.
Looking at isObject()
, we want to be certain that the object we're traversing is actually an object. To find out, we need to verify that the passed value
is not null or undefined, has a typeof
"object," and it is not an array. That last one may look odd. We need to do !Array.isArray()
because in JavaScript, Array
s have a typeof
"object" (meaning that our previous typeof value === "object"
test can be "fooled" by an array being passed).
Assuming that isObject()
returns true
for the value we passed it, we can start to traverse the object. If not, as a fallback, from our findNestedObject()
function we return null
to signify that we did not find a match.
/index.js
const isObject = (value) => {
return !!(value && typeof value === "object" && !Array.isArray(value));
};
const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
if (isObject(object)) {
const entries = Object.entries(object);
for (let i = 0; i < entries.length; i += 1) {
const [treeKey, treeValue] = entries[i];
if (treeKey === keyToMatch && treeValue === valueToMatch) {
return object;
}
}
}
return null;
};
Adding some complexity, now, we want to begin the traversal of our object. By "traverse" we mean looping over each key/value pair on the object
passed in to findNestedObject()
.
To do that loop, we first call to Object.entries()
passing in our object
. This will return us an array of arrays, where each array contains the key
of the key/value pair currently being looped over as the first element and the value
of the key/value pair currently being looped over as the second element. Like this:
const example = {
first: 'thing',
second: 'stuff',
third: 'value',
};
Object.entries(example);
[
['first', 'thing'],
['second', 'stuff'],
['third', 'value']
]
Next, with our array of key/value pairs (entries), we add a for
loop to iterate over the array. Here, i
will equal the index of the current key/value pair we're looping over. We want to do that until we've looped over all entires so we say "run this loop while i < entries.length
and for each iteration, and 1
to the current index i
."
Inside of the for
loop, we use JavaScript array destructuring to access the current key/value pair array (denoted by entries[i]
), assigning each a variable. Here, we assign the first element to the variable objectKey
and the second element to the variable objectValue
.
Remember: our goal is to find an object by the passed keyToMatch
and valueToMatch
. In order to find a match, we need to check each key and value on our object
to see if they're a match. Here, assuming that we find a match, we return the object
as it fulfilled the requirement of having the keyToMatch
and valueToMatch
.
Adding recursion to traverse objects of an arbitrary depth
Now for the fun part. Right now, our function can only loop over a single-level depth object. This is great, but remember, we want to search for a nested object. Because we don't know where that object might be in the "tree" (a nickname you will occasionally hear for an object of nested objects), we need to be able to "keep going" if one of the values in the key/value pairs is itself an object.
This is where our recursion comes in.
/index.js
const isObject = (value) => {
return !!(value && typeof value === "object" && !Array.isArray(value));
};
const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
if (isObject(object)) {
const entries = Object.entries(object);
for (let i = 0; i < entries.length; i += 1) {
const [objectKey, objectValue] = entries[i];
if (objectKey === keyToMatch && objectValue === valueToMatch) {
return object;
}
if (isObject(objectValue)) {
const child = findNestedObject(objectValue, keyToMatch, valueToMatch);
if (child !== null) {
return child;
}
}
}
}
return null;
};
Remember our staircase analogy from earlier. At this point, we've only walked down one step. In order to step down to the next step, we need to tell our function to call itself again.
In this case, we know there is another "step" or object to traverse if passing objectValue
to the isObject()
function we set up earlier returns true
. If it does, that means that we need to check if that object contains the keyToMatch
and valueToMatch
we're looking for.
To traverse that object, we recursively (meaning, to call the function we're currently inside of again), passing in the objectValue
along with the original keyToMatch
and keyToValue
(what we're looking for hasn't changed, just the object we want to look at).
If our recursive call finds a match (meaning our recursive call to findNestedObject()
does not return null
), we return that object child
. Assuming that our recursive call to findNestedObject()
did not return a match, our traversal would stop. If our child itself had nested objects (keeping with our analogy, another "step" to walk down), again, we'd call findNestedObject()
.
Because this code is recursive, it will run until it either finds a matching object, or, exhausts the available nested objects to search.
Now for a test. Let's try to find the object in this tree with a name
field equal to "Down here!"
/index.js
const isObject = (value) => {
return !!(value && typeof value === "object" && !Array.isArray(value));
};
const findNestedObject = (object = {}, keyToMatch = "", valueToMatch = "") => {
if (isObject(object)) {
const entries = Object.entries(object);
for (let i = 0; i < entries.length; i += 1) {
const [objectKey, objectValue] = entries[i];
if (objectKey === keyToMatch && objectValue === valueToMatch) {
return object;
}
if (isObject(objectValue)) {
const child = findNestedObject(objectValue, keyToMatch, valueToMatch);
if (child !== null) {
return child;
}
}
}
}
return null;
};
const staircase = {
step: 5,
nextStep: {
step: 4,
nextStep: {
step: 3,
nextStep: {
step: 2,
nextStep: {
name: "Down here!",
step: 1,
},
},
},
},
};
const match = findNestedObject(staircase, "name", "Down here!");
console.log(match);
// { name: "Down here!", step: 1 }
const match2 = findNestedObject(staircase, "step", 3);
console.log(match2);
// { step: 3, nextStep: { step: 2, nextStep: { name: "Down here!", step: 1 } } }
Here's a quick demo of this running in real-time:
Wrapping up
In this tutorial, we learned how to recursively traverse an object using JavaScript. We learned how to create a base function that was able to loop over the keys of an object we passed it, looking for a matching key and value pair. Then, we learned how to use that function recursively, calling it from within itself if the value of the key/value pair we were currently looping over was an object.