Skip to content

Instantly share code, notes, and snippets.

@JoshDevHub
Last active July 7, 2025 18:31
Show Gist options
  • Save JoshDevHub/b00125f483d4a1ecc257eaa030916973 to your computer and use it in GitHub Desktop.
Save JoshDevHub/b00125f483d4a1ecc257eaa030916973 to your computer and use it in GitHub Desktop.
Recursive Contains
// solution to problem #6 in recursive exercises
function contains(object, searchValue) {
// because `null` has a typof "object", we have to explicitly check
// to prevent trying to access `null`'s values (which don't exist)
if (typeof object !== "object" || object === null) {
return object === searchValue;
}
for (const value of Object.values(object)) {
// An important problem in the code quiz solution is that `return contains()` will only
// search the first property of an object, as it will return whatever the result for it is.
// If our value was nested within the second property, for example, it would never get checked
// even if the first nested object did not contain it.
if (contains(value, searchValue)) {
return true;
}
}
return false;
}
@anasamer056
Copy link

anasamer056 commented Mar 1, 2024

Here's another solution that's slightly shorter:

function contains(obj, target) {
  for (let val of Object.values(obj)) {
    if (val === target) return true;
    if (val.constructor === Object) {
      return contains(val, target);
    }
  }
  return false;
} 

@heren1heren
Copy link

can you add more explanation how the function still works even if the input Object contains more than 1 nested object?

@Mekintosz
Copy link

can you add more explanation how the function still works even if the input Object contains more than 1 nested object?

The function loops through a given object, going deeper with every iteration. On every iteration it checks for given serchValue, if the serch value is present it returns true if another object if found it call Itself on that object( contains(foundObject, serchValue) ). If the last nested object is reched and serchValue is not present it returns false.

@JoshDevHub
Copy link
Author

JoshDevHub commented Mar 7, 2024

@heren1heren Might be easiest to understand by walking through the stack with a debugger.

@anasamer056 Your solution has two important problems, which can be seen by testing like:

const obj = {
  a: {
    foo: "bar"
  },
  b: null,
  c: {
    needle: 42
  }
}

contains(obj, 42) // should return `true`

Your solution can't search multiple objects. It'll search the object under key a: and decide that 42 isn't there. Your solution also doesn't tolerate null values and will error if it encounters one.

Now you can absolutely make things shorter, but doing so can risk losing some clarity.

function contains(obj, target) {
  return obj && Object.values(obj).some((val) => {
    return typeof val === "object" ? contains(val, target) : val === target;
  })
}

I wanted to make it a bit easier to understand what's happening.

@anasamer056
Copy link

@JoshDevHub Thanks for taking the time to review my code!

You're absolutely correct. I guess I was sleep deprived when I was writing that code because I can't believe that I missed these bugs 😅.

@slic23
Copy link

slic23 commented Mar 18, 2024

function nested(objeto, item) {
for (let key in objeto) {
if (objeto[key] === item) {
return true;
}
if (typeof objeto[key] === 'object') {
if (nested(objeto[key], item)) {
return true;
}
}
}
return false;
}

@mayorr-star
Copy link

Can I get an iterative approach?

@jconnorbuilds
Copy link

jconnorbuilds commented Jun 9, 2024

Just adding another recursive solution here that utilizes Array.prototype.some, nice and succinct:

function contains(obj, targetValue) {
  if (typeof obj !== 'object' || obj === null) return false;

  return Object.values(obj).some(
    (value) => value === targetValue || contains(value, targetValue),
  );
}

I had originally used a ternary statement (below) as the callback for .some(), which felt a bit easier to read to me, but for the sake of keeping it short, modified to the above.

function contains(obj, targetValue) {
  if (typeof obj !== 'object' || obj === null) return false;

  return Object.values(obj).some((value) =>
    value === targetValue  ? true : contains(value, targetValue),
  );
}

@tg0xff
Copy link

tg0xff commented Jul 21, 2024

Came up with this function myself:

function contains(obj, targetVal) {
  for (const currentVal of Object.values(obj)) {
    if (typeof currentVal === "object") {
      return contains(currentVal, targetVal);
    }
    if (currentVal === targetVal) return true;
  }
  return false;
}

Felt quite good about it even though I knew that a null could very easily render it useless. But then I realised that I could just change the third line to: if (typeof currentVal === "object" && currentVal !== null) { to prevent that from happening.

@oyinadeolawoyin
Copy link

oyinadeolawoyin commented Nov 29, 2024

I thought I needed to search for the keys and values:

function contains(obj, item) {
if (Object.values(obj).length === 0) return false;
else if (item in obj || Object.values(obj).includes(item)) return true;
else {
for (let value of Object.values(obj)) {
return contains(value, item);
}
}
}

@kazeneza-zephilin
Copy link

kazeneza-zephilin commented Jul 5, 2025

Here is a more improved answer that even runs for flat objects as well as nested ones.

The provided first code doesn't check directly the equality of the value; it directly jump to nested, leaving the flat object unchecked
here is the improved code

function contains(object, searchValue) {
    if (typeof object !== "object" || object === null) {
        return object === searchValue;
    }

    for (const value of Object.values(object)) {
        // First check direct equality
        if (value === searchValue) {
            return true;
        }

        // Then check nested objects
        if (typeof value === "object" && value !== null) {
            if (contains(value, searchValue)) {
                return true;
            }
        }
    }
    return false;
}

OR

function contains(object, searchValue) {
  // Handle non-objects
  if (typeof object !== "object" || object === null) {
    return object === searchValue;
  }

  // Check all values
  for (const value of Object.values(object)) {
    // Direct match OR recursive match
    if (value === searchValue || contains(value, searchValue)) {
      return true;
    }
  }
  return false;
}

@JoshDevHub
Copy link
Author

Unless I'm missing something, the given solution in the original post works with flat objects just fine @kazeneza-zephilin

Your solution is also more repetitive, effectively adding the exact same guard check twice. This might have some marginal performance gains by avoiding new stack frames in certain contexts, but I think it's unlikely to be meaningful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment