generics
I'd probably write it using generics, such as T below -
function hello (name: string): string {
  return `hello ${name}`;
}
function loop<T>(n: number, f: (x: T) => T, x: T): T {
  return n > 0 ? loop(n-1, f, f(x)) : x;
}
console.log(loop(3, hello, "world"));
// hello hello hello world
Which compiles to -
"use strict";
function hello(name) {
    return `hello ${name}`;
}
function loop(n, f, x) {
    return n > 0 ? loop(n - 1, f, f(x)) : x;
}
console.log(loop(3, hello, "world"));
// hello hello hello world
 
 
don't blow the stack
Direct recursion can cause a stack-overflow in JavaScript. If you want to loop a function thousands of times, you will need to convert it to a while loop one way or another -
Here's a simple way -
function hello (name: string): string {
  return `hello ${name}`
}
function loop<T>(n: number, f: (x: T) => T, x: T): T {
  while (n-- > 0) x = f(x)
  return x
}
console.log(loop(20000, hello, "world"));
// hello hello hello ... hello world
Which compiles to -
"use strict";
function hello(name) {
    return `hello ${name}`;
}
function loop(n, f, x) {
    while (n-- > 0)
        x = f(x);
    return x;
}
console.log(loop(20000, hello, "world"));
// hello hello hello ... hello world