LearnFrenzy
Sign in Take a free test

Methods · Lesson 19 of 42

Pass-by-Value and Recursion

5 min read Updated Nov 7, 2026 Saurabh Samir

Two ideas complete your understanding of methods. First, how arguments are passed – "Is Java pass-by-value or pass-by-reference?" is asked in almost every Java interview, and most candidates get it slightly wrong. Second, recursion: a method solving a problem by calling itself on a smaller version of it.

In this lesson

  • Java is always pass-by-value – and why objects seem different
  • Changing an object vs reassigning a parameter
  • Recursion: base case, recursive case, call stack
  • When recursion is a good idea – and when it isn't

Java is always pass-by-value

When you call a method, Java copies the value of each argument into the parameter. For primitives the value is the number itself, so the method can't affect the caller's variable:

public class Main {
    static void addBonus(int marks) {
        marks += 10;                      // changes the copy only
        System.out.println("inside: " + marks);
    }

    public static void main(String[] args) {
        int marks = 70;
        addBonus(marks);
        System.out.println("outside: " + marks);
    }
}

Output

inside: 80
outside: 70

Objects: the reference is copied

For objects and arrays, the variable holds a reference. Java copies that reference – so the method gets a second reference to the same object. It can change the object's contents, but reassigning the parameter only changes its own copy.

import java.util.Arrays;

public class Main {
    static void addBonus(int[] marks) {
        marks[0] += 10;                   // changes the shared array
    }

    static void replace(int[] marks) {
        marks = new int[]{0, 0, 0};       // reassigns the local copy only
    }

    public static void main(String[] args) {
        int[] marks = {70, 80, 90};
        addBonus(marks);
        System.out.println(Arrays.toString(marks));
        replace(marks);
        System.out.println(Arrays.toString(marks));
    }
}

Output

[80, 80, 90]
[80, 80, 90]

What gets copied

  1. main: marks → array #1the caller's reference
  2. call addBonus(marks)the reference is copied into the parameter
  3. both point to array #1marks[0] += 10 is visible to main
  4. replace: marks = new arrayonly the copy moves; main still sees array #1

The interview answer

"Java is strictly pass-by-value. For objects, the value passed is a copy of the reference, so a method can modify the object it points to but cannot make the caller's variable point to a different object." The classic proof: a swap(a, b) method on two object references can never swap the caller's variables.

Recursion

A recursive method calls itself. Every correct recursive method has two parts:

  • a base case that is answered directly and stops the recursion;
  • a recursive case that calls the method on a smaller input, moving towards the base case.
public class Main {
    static long factorial(int n) {
        if (n <= 1) return 1;            // base case
        return n * factorial(n - 1);     // recursive case
    }

    public static void main(String[] args) {
        System.out.println(factorial(5));
        System.out.println(factorial(20));
    }
}

Output

120
2432902008176640000

factorial(4) on the call stack

  1. factorial(4)waits for 4 × factorial(3)
  2. factorial(3)waits for 3 × factorial(2)
  3. factorial(2)waits for 2 × factorial(1)
  4. factorial(1) = 1base case – no more calls
  5. Unwinding2 × 1 = 2 → 3 × 2 = 6 → 4 × 6 = 24

More recursive classics

public class Main {
    static int sumDigits(int n) {
        return n < 10 ? n : n % 10 + sumDigits(n / 10);
    }

    static boolean isPalindrome(String s, int i, int j) {
        if (i >= j) return true;
        return s.charAt(i) == s.charAt(j) && isPalindrome(s, i + 1, j - 1);
    }

    static long power(long base, int exp) {         // fast exponentiation
        if (exp == 0) return 1;
        long half = power(base, exp / 2);
        return exp % 2 == 0 ? half * half : half * half * base;
    }

    static int binarySearch(int[] a, int key, int lo, int hi) {
        if (lo > hi) return -1;
        int mid = (lo + hi) >>> 1;
        if (a[mid] == key) return mid;
        return a[mid] < key ? binarySearch(a, key, mid + 1, hi) : binarySearch(a, key, lo, mid - 1);
    }

    public static void main(String[] args) {
        System.out.println(sumDigits(4096));
        System.out.println(isPalindrome("racecar", 0, 6));
        System.out.println(power(2, 40));
        System.out.println(binarySearch(new int[]{3, 8, 15, 23, 42, 57}, 42, 0, 5));
    }
}

Output

19
true
1099511627776
4

The cost of recursion

Every call adds a stack frame. Without a base case – or with very deep input – the stack runs out:

public class Main {
    static int depth = 0;

    static void dive() {
        depth++;
        dive();                // no base case
    }

    public static void main(String[] args) {
        try {
            dive();
        } catch (StackOverflowError e) {
            System.out.println("StackOverflowError after many calls: " + (depth > 1000));
        }
    }
}

Output

StackOverflowError after many calls: true

Recursion can also repeat work. Naive Fibonacci recalculates the same values exponentially many times; storing results (memoisation) or using a loop fixes it:

public class Main {
    static long[] memo = new long[91];

    static long fib(int n) {
        if (n < 2) return n;
        if (memo[n] != 0) return memo[n];
        return memo[n] = fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] args) {
        System.out.println(fib(10) + " " + fib(50) + " " + fib(90));
    }
}

Output

55 12586269025 2880067194370816120
Use recursion forPrefer a loop for
Trees, folders, nested data, divide-and-conquer (merge sort, quick sort), backtrackingSimple counting, sums, linear scans – anything that may go thousands of levels deep

Practice

Try it yourself

  1. Write a recursive method that reverses a string.

    Show solution
    public class Main {
        static String reverse(String s) {
            if (s.length() <= 1) return s;
            return reverse(s.substring(1)) + s.charAt(0);
        }
    
        public static void main(String[] args) {
            System.out.println(reverse("LearnFrenzy"));
        }
    }

    Output

    yznerFnraeL
  2. Recursively compute the GCD of 84 and 36 (Euclid: gcd(a, b) = gcd(b, a % b)).

    Show solution
    public class Main {
        static int gcd(int a, int b) {
            return b == 0 ? a : gcd(b, a % b);
        }
    
        public static void main(String[] args) {
            System.out.println(gcd(84, 36));
        }
    }

    Output

    12

Quick check

Is Java pass-by-reference for objects?

No. It is always pass-by-value; for objects the value is a copy of the reference.

Can a method change the caller's array elements?

Yes – it receives a reference to the same array.

What happens if a recursive method has no base case?

It keeps calling itself until the stack is exhausted and Java throws StackOverflowError.

Summary

  • Java copies argument values; for objects it copies the reference, so contents can change but the caller's variable can't be reassigned.
  • Recursion needs a base case and must move towards it; every call uses a stack frame.
  • Memoise repeated work; prefer loops when depth could be large.

Interview practice: Core Java interview questions.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular