Phase 1 · BeginnerModule 6~34 min read

Methods & Program Decomposition

Break programs into reusable methods and understand parameters, return values, overloading, and recursion.

What you'll learn

As programs grow, cramming everything into main becomes a tangled mess. Methods let you break a program into small, named, reusable pieces — the single most important skill for writing code that stays understandable.

By the end you'll be able to:

  • Define and call methods with parameters and return values
  • Understand variable scope
  • Provide several versions of a method with overloading
  • Explain Java's pass-by-value behaviour — the interview classic
  • Accept a flexible number of arguments with varargs
  • Write recursive methods that call themselves
  • Design clean, single-purpose methods

Defining & calling methods

A method is a named block of code that performs one task. You define it once and call it as many times as you like. Every method has the same shape:

Anatomy of a method
public static int add(int a, int b) {
    return a + b;
}
■

Modifiers — who can call it and how (public, static).

■

Return type — the type it hands back (void if nothing).

■

Name — a verb describing what it does.

■

Parameters — the inputs it needs to do its job.

A method can take parameters (inputs) and hand back a result with return. If it doesn't return anything, its return type is void. The values you actually pass in when calling are called arguments.

Greeter.java
public class Greeter {

    static void greet(String name) {           // void: returns nothing
        System.out.println("Hello, " + name + "!");
    }

    static int square(int n) {                 // returns an int
        return n * n;
    }

    public static void main(String[] args) {
        greet("Sara");                         // call it
        greet("Omar");                         // reuse it
        int result = square(5);
        System.out.println("5 squared is " + result);
    }
}

Tip

A good method does one thing and its name says exactly what. calculateTax, isValidEmail, and sendReceipt tell the whole story without you reading the body.

Local variables & scope

A variable declared inside a method (or a block { }) is local — it exists only there and vanishes when the method ends. Two methods can each have a variable called total without any conflict, because each lives in its own scope. This isolation is what makes methods safe to reuse.

Method overloading

Java lets you give several methods the same name as long as their parameter lists differ (in number or types). This is overloading, and the compiler picks the right version based on the arguments you pass:

Overload.java
static int add(int a, int b)        { return a + b; }
static double add(double a, double b) { return a + b; }
static int add(int a, int b, int c)  { return a + b + c; }

System.out.println(add(2, 3));      // 5   -> int version
System.out.println(add(2.5, 3.5));  // 6.0 -> double version
System.out.println(add(1, 2, 3));   // 6   -> three-arg version

Note

Overloads must differ in their parameters — you can't overload on the return type alone. System.out.println is itself heavily overloaded, which is why it happily prints ints, doubles, strings, and objects.

Pass-by-value in Java

Here's a concept that confuses even experienced developers: Java is always pass-by-value. When you call a method, it receives a copy of each argument.

What actually gets copied

Primitives → a copy of the value

The method gets its own copy. Changing the parameter never affects the caller's variable.

Objects → a copy of the reference

Both variables point to the same object, so changes to its contents are visible. (Reassigning the parameter still doesn't affect the caller.)

For a primitive, that copy is the value, so the original is untouchable. For an object, the copy is the reference (the arrow pointing to the object) — so both the caller and the method point at the same object, and changes to its contents are shared:

PassByValue.java
static void tryToChange(int x) {
    x = 99;              // changes only the local copy
}

static void fillFirst(int[] list) {
    list[0] = 99;        // changes the shared array's contents
}

public static void main(String[] args) {
    int number = 5;
    tryToChange(number);
    System.out.println(number);   // still 5

    int[] data = {1, 2, 3};
    fillFirst(data);
    System.out.println(data[0]);  // now 99
}

Key idea

The primitive number stayed 5, but the array's contents changed. Both variables shared one array — yet neither could reassign the other's variable. That's pass-by-value in a nutshell.

Variable-length arguments (varargs)

Sometimes you don't know how many arguments there'll be. Varargs — Type... name — lets a method accept any number of them, treating them as an array inside:

Varargs.java
static int sum(int... numbers) {     // accepts any count
    int total = 0;
    for (int n : numbers) total += n;
    return total;
}

System.out.println(sum(1, 2, 3));         // 6
System.out.println(sum(10, 20, 30, 40));  // 100
System.out.println(sum());                // 0

Note

A method can have only one varargs parameter, and it must come last. That's how methods like String.format accept any number of values.

Recursion

A recursive method is one that calls itself to solve a smaller version of the same problem. Every recursion needs two things: a base case that stops it, and a recursive case that moves toward that base case. The classic example is factorial:

Recursion.java
static int factorial(int n) {
    if (n <= 1) return 1;             // base case: stops the recursion
    return n * factorial(n - 1);      // recursive case
}

System.out.println(factorial(5));     // 5*4*3*2*1
How factorial(3) unfolds

Calls go down (the stack grows)

factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 ← base case

Returns come back up (the stack unwinds)

↑ returns 1
↑ returns 2 * 1 = 2
↑ returns 3 * 2 = 6
Each call waits for the next; results multiply back up as the stack unwinds.

Always have a base case

Forget the base case (or fail to move toward it) and the method calls itself forever, filling up the call stack until you get a StackOverflowError.

Clean method design

A few habits separate readable code from a mess:

  • One responsibility — each method should do a single, well-defined job
  • Short — if a method scrolls off the screen, it probably wants splitting
  • Descriptive names — verbs that state the effect (saveOrder, not doStuff)
  • Few parameters — many parameters is a hint the method is doing too much
  • Return early — handle edge cases up top to avoid deep nesting

Tip

A great rule of thumb: you should be able to describe what a method does in one short sentence without using the word "and".

Recap & quick check

Key takeaways

  • Methods package code into named, reusable pieces with parameters and an optional return value.
  • Local variables live only inside their method or block (scope).
  • Overloading = same name, different parameter lists; the compiler picks the match.
  • Java is always pass-by-value: primitives copy the value, objects copy the reference.
  • Recursion needs a base case to stop and a recursive case that shrinks the problem.

Quick check

1. What return type does a method use when it returns nothing?

2. How does Java pass arguments to methods?

3. What makes two methods valid overloads?

4. What does every recursive method need to avoid running forever?

5. After passing a primitive int into a method that reassigns it, the caller's variable is…

Superb — you can now structure programs into clean, reusable pieces. Next up: Module 7 — Arrays, where you'll store and process whole collections of values.