Wenn Du eine Funktion in einer Funktion definierst, musst Du die `#` verdoppeln:
\cs_new_protected:Npn \zahlen_vorwaerts:n #1
{
\cs_set:Npn \zahlen_ausgeben:n ##1 {##1}
\int_step_function:nnnN {1} {1} {#1} \zahlen_ausgeben:n
}
Was geschieht, wenn man das nicht macht? Mit Deiner Definition wird aus `\Vorwaerts{5}` im ersten Schritt durch ersetzen aller `#1` mit `5`:
\cs_set:Npn \zahl_ausgeben:n 5 {5}
\int_step_function:nnnN {1} {1} {5} \zahl_ausgeben:n
Das bedeutet, `\zahl_ausgeben:n` wird bei Dir *ohne* Parameter definiert! Kein Wunder, dass es dann Fehler gibt mit gibt! Mit der neuen Definition passiert durch Austauschen aller `#1` mit `5` und aller `##` mit `#`:
\cs_set:Npn \zahlen_ausgeben:n #1 {#1}
\int_step_function:nnnN {1} {1} {5} \zahlen_ausgeben:n
----------
Beachte, dass ich hier `\cs_new_protected:Npn` für `\zahlen_vorwaerts:n` verwendet habe, da die innere Definition von `\zahlen_ausgeben:n` eh dafür sorgt, dass `\zahlen_vorwaerts:n` nicht expandierbar ist.
Auf der anderen Seite: wenn die Zahlen ein einheitliches Format haben sollen, dann wäre sowieso zu empfehlen, die Ausgabefunktion separat zu definieren:
\documentclass{article}
\usepackage{expl3}
\usepackage{xparse}
\ExplSyntaxOn
\cs_new:Npn \zahlen_ausgeben:n #1 {#1}
\cs_new:Npn \zahlen_vorwaerts:n #1
{ \int_step_function:nnnN {1} {1} {#1} \zahlen_ausgeben:n }
\cs_new:Npn \zahlen_rueckwaerts:n #1
{ \int_step_function:nnnN {#1} {-1} {1} \zahlen_ausgeben:n }
\NewDocumentCommand \Vorwaerts {m}
{ \zahlen_vorwaerts:n {#1} }
\NewDocumentCommand \Rueckwaerts {m}
{ \zahlen_rueckwaerts:n {#1} }
\ExplSyntaxOff
\begin{document}
\Vorwaerts{5} \par
\Rueckwaerts{5}
\end{document}