Top-down DFS Tree Diagram

Top-down DFS

/* 
Time Complexity - O(n * 2^n). Work done by leaf nodes (O(n * 2^n)) + work done by internal nodes (O(2^n))
Space Complexity - O(n * 2^n). Input (O(n)) + Auxiliary stack (O(n)) + Output (O(n * 2^n))
*/
var letterCasePermutation = function (str) {
	const result = []

	function recurse(str, i, slate) {
		// Base case
		if (i === str.length) {
			result.push(slate.join('')) //O(n) time
			return
		}

		// numbers
		if (!isNaN(str[i])) {
			slate.push(str[i])
			recurse(str, i + 1, slate)
			slate.pop()
			// letters
		} else {
			// lowercase
			slate.push(str[i].toLowerCase())
			recurse(str, i + 1, slate)
			slate.pop()
			// uppercase
			slate.push(str[i].toUpperCase())
			recurse(str, i + 1, slate)
			slate.pop()
		}
	}

	recurse(str, 0, [])
	return result
}

// Tests
const tests = [
	['a1b2', ['a1b2', 'a1B2', 'A1b2', 'A1B2']],
	['123', ['123']],
	['abc', ['abc', 'abC', 'aBc', 'aBC', 'Abc', 'AbC', 'ABc', 'ABC']],
]

for (const test of tests) {
	const res = letterCasePermutation(test[0])
	console.log(res)
	console.log(JSON.stringify(res) === JSON.stringify(test[1]))
}

With string concatenation (slate is a string):

// With string concatenation (slate is a string)
var letterCasePermutation = function (str) {
	const result = []

	function recurse(str, i, slate) {
		// Base case
		if (i === str.length) {
			result.push(slate) //O(n) time
			return
		}

		// numbers
		if (!isNaN(str[i])) {
			recurse(str, i + 1, slate + str[i])

			// letters
		} else {
			// lowercase
			recurse(str, i + 1, slate + str[i].toLowerCase())
			// uppercase
			recurse(str, i + 1, slate + str[i].toUpperCase())
		}
	}

	recurse(str, 0, '')
	return result
}