When you say bash do you really mean within bash or just scripting using common command-line tools?
Scripting tools together
find . -type f -exec chmod u+x {} \;
What this means is for every file in the current directory make it executable. The \; is passing the ; to the find command which interprets it as "invoke the exec string on each found path individually". You can replace \; with \+ which tells find to first gather all paths & substitute it for {} all at once. Generally \+ can be more efficient but you have to be careful with command-line lengths as there are limits. What you can do then is combine it with xargs:
find . -type f -print0 | xargs -0 -P $(nproc) -I{} chmod u+x {}
What this does is it tells find to use the null character as a terminator instead of newlines. This ensures that you process each entry correct even if it has arbitrary spaces or random UTF characters (\0 is not a valid part of a path). The -0 option to xargs tells it to use \0 as the separator when reading arguments instead of newliens. The -P option says to run the command in parallel N times where in this case N is the output of the nproc command which prints the number of processors. -I is the substitution string and the rest is the command string to process.
The man pages for find & xargs are good to explore.
Natively within Bash
On the off-chance you're looking for a solution wholly within Bash & no external tools, it's a bit more complicated & would involve some more advanced Bash-specific language constructs where you implement find yourself. To iterate over the contents of a directory, you'd do something like for path in <dir>; do. Then you'd use the test built-in [[ -d "$path" ]] to determine if it's a directory, [[ -f "$path" ]] if it's a file etc (man test has many of the explanations but note that's the standalone test executable which has subtle differences & pitfalls from the more feature-filled & safer bash version [[ ]].
Working with bash arrays: https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_02.html
Bash test introduction: https://www.tldp.org/LDP/abs/html/testconstructs.html
What that test introduction doesn't mention is things like regular expressions which would be part of that syntax. Bash also has powerful options for manipulating the contents of variables: https://www.tldp.org/LDP/abs/html/parameter-substitution.html
In practice though, anything even moderately complex (whether within bash or by combining tools), is probably better maintained & easier to read in Python (speaking as someone with lots and lots of extensive experience in Bash).
find_files() {
  if [[ ! -x "$1" ]]; then
     echo "$1 isn't a directory" >&2
     return 1
  fi
  local dirs=("$1")
  while [[ "${#dirs[@]}" -gt 0 ]]; do
    local dir="${dirs[0]}"
    dirs=("${dirs[@]:1}") # pop the element from above
    # whitespace in filenames iterated will be a problem. Look to the IFS
    # variable to handle those more gracefully.
    for p in "${dir}"/*; do 
      if [[ -d "$p" ]]; then
         dirs+=("$p")
      elif [[ -f "$p" ]]; then
         echo "$p"
      fi
    done
  done
}
for f in $(find_files .); do
    chmod u+x "$f"
done
As you can see this is more complicated, trickier, & slower than just using the find/xargs binaries. You would never want to write something like that in reality. You could even get fancier where you convert find_files into process by having it take a command that you then evaluate as you're iterating (instead of echo'ing the path) via eval. eval is super tricky & can be a security exploit.