There are three problems.
First you need an initial case for your recursion. The following leads to infinite recursion (the value of i constantly decreasing, but never stops).
f <- function(i) g(i-1) g <- function(i) g(i-1) + f(i) f(5)
The following will stop.
f <- function(i) g(i-1) g <- function(i) if( i <= 0 ) 0 else g(i-1) + f(i) f(5)
The second problem is that some of these values ββwill be recounted exponentially.
f(500)
In more abstract terms, consider a graph whose vertices f(i) and g(i) , for all values ββof i , with edges corresponding to function calls. Recursion allows you to explore this graph as if it were a tree. But in this case, it is not a tree, and you end up evaluating the same function (exploring the same node) many times. The following code draws this graph.
library(igraph) n <- 5 g <- graph.empty() g <- g + vertices( paste0("f(", 1:n, ")" ) ) g <- g + vertices( paste0("g(", 0:n, ")" ) ) for( i in 1:n) { g <- g + edge( paste0("f(", i ,")"), paste0( "g(", i-1, ")" ) ) g <- g + edge( paste0("g(", i ,")"), paste0( "f(", i, ")" ) ) g <- g + edge( paste0("g(", i ,")"), paste0( "g(", i-1, ")" ) ) } plot(g)

One workaround is to save the values ββthat you have already calculated to avoid recalculating them: this is called memoization .
library(memoise) f <- function(i) G(i-1) g <- function(i) if( i <= 0 ) 1 else G(i-1) + F(i) F <- memoize(f) G <- memoize(g) f(500)
When you memoise a function, the number of recursive calls becomes linear, but it can still be too large. You can increase the limit as suggested in the original error message:
options( expressions = 5e5 )
If this is not enough, you can pre-populate the table using ever larger i values. In your example:
options( expressions = 5e5 ) loss(1000,10)
Thirdly, there may be function calls that unnecessarily increase the size of the call stack. In your example, if you type traceback() after an error, you will see that many intermediate functions are in the call stack because weight(i,k) and loss(i,k) are used inside the function arguments. If you move these calls outside the function arguments, the call stack is smaller and it seems to work.
library(memoise) gradient <- function(x,y,tau){ if (xy > 0) { - tau } else { (1-tau) } } aj <- c(-3,-4,-2,-3,-5,-6,-4,-5,-1,rep(-1,15)) f <- function(x,vec){sum(x^vec)-1} root <- uniroot(f, interval=c(0,200), vec=aj)$root memloss<-function(i,k){ cat( "loss(", i, ",", k, ")\n", sep="" ) if (i==1) { c(rep(0,24)) } else if (i <= 0 | k < -5) { 0 } else { w <- weight(i-1,k)