Topics › Basics › Learn Basic Recursion
Print 1 to N using Recursion
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given a positive integer N. Print the numbers 1, 2, 3, ..., N in increasing order without using a loop: the only tool allowed for repetition is recursion.
This is a warm-up for recursion, so the point is not the output (a for loop does it in one line) but the two different ways a recursive function can produce a sequence: doing the work before the recursive call, or after it. Both appear again and again in harder problems, so it is worth seeing them side by side on the simplest possible task.
Constraints: 1 ≤ N ≤ 10^4 in the usual statement. We also treat N ≤ 0 as “print nothing” so the base case is exercised.
To make the behaviour testable, the methods below collect the numbers into a List<Integer> instead of printing them. Replace out.add(x) with System.out.println(x) and you have the printing version.
Example 1
N = 5
output = 1 2 3 4 5 // every number from 1 up to N, each once, in order
Example 2
N = 1
output = 1 // a single number, the smallest non-trivial case
Example 3
N = 0
output = (nothing) // there is no number between 1 and 0
Approach 1: The loop, as a reference
Intuition. Before replacing the loop, write it down so we know exactly what the recursion must reproduce: a counter i that starts at 1, emits itself, and advances to i + 1 until it passes N. Each recursive version below is a different way of simulating that counter with function calls.
Algorithm.
- Create an empty list.
- For
ifrom1toN, appendi. - Return the list.
static List<Integer> iterative(int n) {
List<Integer> out = new ArrayList<>();
for (int i = 1; i <= n; i++) out.add(i);
return out;
}
Complexity. Time O(n): one append per number. Space O(1) beyond the output list.
Approach 2: Parameterised recursion, work first then recurse
Intuition. A loop keeps its state in the counter i. Recursion has no counter, but it has parameters: pass i as an argument and let each call represent one iteration. The call for i does the work for that iteration (emit i) and then asks the next call to handle i + 1. The chain stops when i exceeds N, which is exactly the loop’s exit condition turned into a base case.
The order of the two statements matters: because we emit before recursing, the numbers come out on the way down the call chain, so the first call prints 1, the second prints 2, and so on.
Algorithm.
- Start with
forward(1, n, out). - In
forward(i, n, out): ifi > n, return (base case). - Otherwise append
i, then callforward(i + 1, n, out).
static List<Integer> recursiveForward(int n) {
List<Integer> out = new ArrayList<>();
forward(1, n, out);
return out;
}
private static void forward(int i, int n, List<Integer> out) {
if (i > n) return; // base case: nothing left to print
out.add(i); // do the work ...
forward(i + 1, n, out); // ... then recurse
}
Complexity. Time O(n): exactly n + 1 calls, each doing constant work. Space O(n) for the call stack: all n frames are alive at once when the base case is reached, because no call returns until the one below it does.
Approach 3: Backtracking, recurse first then work
Intuition. Now flip the two statements. If the call for n first asks the recursion to handle 1 .. n - 1 and only then emits n, the output is still increasing: by the time we append n, everything smaller has already been appended by the calls deeper in the stack. The numbers come out on the way up, as the calls return. This “do the work while unwinding” shape is what people mean by backtracking in basic recursion.
Notice that this version needs no extra parameter: the problem for n is defined in terms of the problem for n - 1, which is the most natural recursive definition of “the list 1 to n” (it is “the list 1 to n - 1, followed by n”).
Algorithm.
- Start with
backtrack(n, out). - In
backtrack(n, out): ifn < 1, return (base case). - Otherwise call
backtrack(n - 1, out)first, then appendn.
static List<Integer> recursiveBacktrack(int n) {
List<Integer> out = new ArrayList<>();
backtrack(n, out);
return out;
}
private static void backtrack(int n, List<Integer> out) {
if (n < 1) return; // base case
backtrack(n - 1, out); // recurse first ...
out.add(n); // ... print on the way back
}
Dry run on N = 3. Read the table top to bottom: calls go down until the base case, then the appends happen as each frame returns.
| call | action before recursing | recursive call | action after it returns |
|---|---|---|---|
backtrack(3) |
none | backtrack(2) |
append 3 |
backtrack(2) |
none | backtrack(1) |
append 2 |
backtrack(1) |
none | backtrack(0) |
append 1 |
backtrack(0) |
n < 1, return |
The appends execute in the order backtrack(1), backtrack(2), backtrack(3) finish, so the list becomes [1, 2, 3].
Complexity. Time O(n): n + 1 calls. Space O(n) for the call stack, same as Approach 2.
Which recursion shape should you reach for?
- Parameterised (Approach 2) mirrors a loop. Whenever you have a loop with a counter and want to express it recursively, pass the counter as an argument. It is also the shape that turns into tail recursion, where the recursive call is the last statement.
- Backtracking (Approach 3) mirrors a recursive definition. It is the shape you need when the result for
nis assembled from the result forn - 1, and it is the same idea that makes Print N to 1 using Recursion a one-line change: swap which side of the call the work happens on and the order of the output flips.
Both are correct here. In later problems (subsets, permutations, tree traversals) you will often need both at once: do something before the call, recurse, undo or record something after the call.
Edge cases and pitfalls
N = 0or negative. Both recursive versions hit the base case immediately and produce an empty list. Make sure the base case is checked before any work is done, otherwise0would be printed.- Off-by-one in the base case. The forward version stops at
i > n, noti >= n; the latter would dropN. The backtracking version stops atn < 1, notn <= 1; the latter would drop1. Test withN = 1to catch both. - Stack depth. Each level of recursion is one stack frame. Java’s default stack comfortably handles tens of thousands of frames of this size; the tests go to
N = 2000. ForNin the millions you would get aStackOverflowError, and the loop is the right tool. - Printing versus collecting. Printing inside the recursion is fine for a console exercise, but collecting into a list (as here) makes the function testable and lets the caller decide how to display the result.
- Mutable shared state. The list is passed down as a parameter and mutated. That is safe here because every call appends exactly once. Avoid
staticlists that survive between calls; they make the second invocation return stale data.
Related problems
- Print N to 1 using Recursion is the mirror image: swap the roles of the two recursion shapes.
- Understand recursion by print something N times and Print name N times using recursion are the even simpler versions where the printed value does not change.
- Sum of First N Numbers uses the same two shapes to compute instead of print.
- Factorial of a given number replaces the sum with a product.
- Reverse an array applies parameterised recursion with two moving indices.