Overloading, Scope, and Decomposition
Same name different signature, variables that die at the brace, and decomposing a problem into named verbs
In a nutshell
Three ideas, in order of how often they show up in real code.
- Overloading. Two methods can share a name as long as their parameter lists differ in type or count. The return type is not part of the comparison.
print(int)andprint(double)are two different methods that happen to share a name. - Local scope. A variable declared inside a method (parameter or local) exists only for the duration of that one call. When the method returns, the variable ceases to exist. Another method, or another call to the same method, cannot reach it.
- Decomposition. The whole point of having methods is to take a verbose
mainand break it into 3 to 5 named verbs, each doing one thing. The skill is naming the verbs well.
Today in three sentences. Overload by changing the parameter list, never just the return type. Locals die at the closing brace. A program is a small set of well-named verbs you call from
main.
After this lesson, you will be able to:
- Decide whether two method headers form a legal overload pair or a duplicate-signature compile error.
- State why a variable declared inside one method cannot be accessed from another method (the local-scope rule).
- Read an English problem statement and sketch a
mainthat decomposes it into 3 to 5 named static-method calls. - Distinguish overloading (same name, different parameters, resolved at compile time) from overriding (same name and same parameters, in a subclass — a Week 8 conversation).
From CSCD 110. Python does not have overloading at all: a name in a module refers to exactly one function, and redefining it shadows the earlier one. Java overloading is closer to what Python achieves with default arguments and type-dispatch tricks (
@singledispatch), but more rigid: every overload is its own separate method, with its own header and body.
Method signatures and overloading
A method’s signature is its name plus the types and arity of its parameter list. Arity means “how many parameters.” The return type is not part of the signature.
public static int add(int a, int b) // signature: add(int, int)
public static double add(double a, double b) // signature: add(double, double)
public static int add(int a, int b, int c) // signature: add(int, int, int)
All three methods are named add. Their signatures differ — different parameter types, or a different count — so the compiler treats them as three separate methods that happen to share a name. This is method overloading, and it is legal.
When you write a call like add(3, 4), the compiler looks at the argument types and picks the matching signature. add(3, 4) has two int arguments, so the compiler picks the first overload. add(3.0, 4.0) has two double arguments, so the compiler picks the second. add(1, 2, 3) has three int arguments, so the compiler picks the third. The dispatch happens at compile time.
You have already used overloading. System.out.println is overloaded for every primitive type and for String, which is why you can call it on an int, a double, a char, or a String without thinking about it. There is no single println method; there are about a dozen of them.
The signature comparison rule has one important sharp edge: changing only the return type does not produce a new overload. It produces a compile error.
public static int foo(int x) { return x; }
public static double foo(int x) { return x; } // compile error: duplicate method
Both methods have the signature foo(int). The compiler cannot tell them apart by looking at a call site (foo(5) would match either), so it refuses to compile. If you want a different return type, you have to change the parameter list too.
Common pitfall: trying to overload by return type alone. Some students see two methods with different return types and think the compiler can figure out which one was intended from context. It cannot. The signature comparison ignores return type. If two methods would otherwise have identical signatures, you have a duplicate, not an overload.
Check your understanding. Which pairs are legal overloads (both methods can coexist)? For each pair, decide: legal, or duplicate-signature error?
A.
int max(int a, int b)anddouble max(double a, double b)B.void log(String msg)andint log(String msg)C.int sum(int[] arr)andint sum(int a, int b)D.boolean isEven(int n)andboolean isEven(long n)Reveal answer
A. Legal. Different parameter types (
int, intvsdouble, double). B. Duplicate-signature error. Same name and same parameter list (String); changing only the return type does not disambiguate. C. Legal. Different parameter types (int[]vsint, int) AND different arity. D. Legal. Same name, but different parameter types (intvslong).
A second sharp edge worth remembering: keep the behavior of overloads consistent. If add(int, int) does addition, add(double, double) should also do addition. Overloading is a convenience, not a license to give the same name to two unrelated methods. A reader who sees add(...) should be able to predict what it does without checking which overload was picked.
Check your understanding. Look at this pair of headers. Do they overload, conflict, or compile cleanly for a different reason?
public static int print(int x) { return x; } public static String print(int x) { return "" + x; }Reveal answer
Compile error: duplicate signature
print(int). The return types are different but Java does not include return type in the signature comparison. This is a duplicate, not an overload.
Local scope: variables that die at the brace
Every variable in Java has a scope — a region of code in which the name is valid. Parameters and local variables of a method have scope that begins at the line where they are declared and ends at the matching closing brace }.
public static int square(int n) {
int result = n * n; // result: scope starts here
return result;
} // result: scope ends here
public static void main(String[] args) {
int r = square(5);
System.out.println(r); // OK: r is in scope
System.out.println(result); // compile error: result not in scope
}
Inside square, the parameter n and the local result both exist. The moment square returns, both names cease to exist. They cannot be reached from main, from another method, or from a future call to square. (A future call to square will create its own fresh n and result, with their own values.)
Scope also applies inside any block of curly braces, including loop bodies and if branches.
public static void demo() {
for (int i = 0; i < 3; i++) {
int doubled = i * 2; // doubled: scope is just this loop body
System.out.println(doubled);
}
System.out.println(doubled); // compile error: out of scope
System.out.println(i); // compile error: i is also out of scope
}
The for-loop’s i and the body-local doubled both die when the loop ends. The pattern is the same as a method’s: every closing brace ends the scope of every variable declared since the matching opening brace.
This is a feature, not a limitation. Tight scope prevents one part of your program from accidentally reading or modifying a variable another part of your program relied on. It also means you can reuse a name like i or result in different methods without worrying about collision.
Common pitfall: declaring a variable inside an
ifbody and trying to use it after theif. This is one of the most common scope errors in CS1.if (score >= 60) { String grade = "P"; } System.out.println(grade); // compile error: grade not in scopeThe fix: declare
gradebefore theif, and assign it inside the branches. Thengradeis in scope for theprintln.
Check your understanding. Which of the following compile? For the ones that don’t, explain why.
A.
public static int doubleIt(int x) { int y = x * 2; return y; } public static void main(String[] args) { int n = doubleIt(7); System.out.println(y); }B.
public static int square(int x) { int x = x * x; // declaring a new local x return x; }Reveal answer
A. Does not compile.
yis local todoubleIt; inmain, the nameyis unknown.B. Does not compile either, but for a different reason:
xis already in scope as the parameter, so trying to declare another local also namedxis a duplicate-variable error in the same scope. (You can either rename the local, or just writex = x * x;to update the parameter directly.)
Overloading is not overriding
You will hear the words overloading and overriding for the rest of this course. They sound similar and they mean different things. Get the difference straight now.
- Overloading: same name, different parameter list, both methods live in the same class. The compiler picks which one to call by looking at the argument types at the call site. This is what you learned above.
- Overriding: same name AND same parameter list, but the second method lives in a subclass of the first. The runtime picks which one to call based on the actual type of the receiver object. (You will not write subclasses until Week 8 when you have classes and inheritance.)
For now, you only have static methods in a single class, so the only thing you can do is overload. Overriding is parked for later. The two-sentence summary is enough today.
Decomposition: a small program is a list of named verbs
You have all the pieces. The remaining skill of Week 5 is design: how do you take an English problem statement and turn it into a small set of methods, each doing one thing?
Read this problem.
Read a list of test scores from the user (a sentinel of
-1ends the input). Compute and print the count, the average, and the highest score.
A first instinct is to write the whole thing in main. That works, but the result is a 30-line wall of code where the structure is implicit. Better: spend a minute decomposing the problem before you write any code. Look for verbs in the problem statement.
Read a list of test scores. Compute the count. Compute the average. Compute the highest. Print the report.
Each verb is a candidate method. Sketch the method headers (return type + name + parameters) without writing any bodies yet:
public static int[] readScores(Scanner kb)
public static int countScores(int[] scores) // length, basically
public static double averageScore(int[] scores)
public static int highestScore(int[] scores)
public static void printReport(int count, double avg, int high)
Now main reads like the four-line summary the user asked for:
public static void main(String[] args) {
Scanner kb = new Scanner(System.in);
int[] scores = readScores(kb);
printReport(countScores(scores), averageScore(scores), highestScore(scores));
}
(You will see the int[] type formally in lesson 6a; for now the point is the shape of main.)
Three guidelines for naming the verbs.
(1) Verb in camelCase. readScores, letterGrade, printReport. A method does something — that is what verbs are for. Avoid noun-only names like scores for a method.
(2) One method, one job. If the method’s name needs the word “and” (readAndValidateAndComputeAverage), the method is doing too much. Split it.
(3) The body should fit on one screen. A method body that runs more than 30 lines is usually two methods that have not been separated yet.
These are guidelines, not laws. The end goal is readability. A reader (including future you) should be able to read main once and know what the program does, then jump into individual methods only as needed.
Common pitfall: methods that print and compute. A method called
getAverageAndPrintItis two methods. Split it intoaverageScore(int[])(returns adouble) andprintReport(...)(prints). Now you can compute averages without printing them, and print reports without recomputing averages. Each method does one thing well.
Check your understanding. A problem says: “Ask the user for a year. Decide whether it is a leap year. Print ‘yes’ or ‘no’.” Sketch a
mainthat decomposes this into method calls. Give each method a header (return type + name + parameter list). Do not write the bodies.Reveal answer
One reasonable decomposition (other shapes are also fine):
public static int readYear(Scanner kb) // returns the year public static boolean isLeapYear(int year) // returns yes/no public static void main(String[] args) { Scanner kb = new Scanner(System.in); int year = readYear(kb); if (isLeapYear(year)) System.out.println("yes"); else System.out.println("no"); }Two methods, each doing one thing:
readYearhandles input,isLeapYearhandles the predicate.mainis the four-line outline. The print stays inmainbecause it is conditional and uses the predicate’s result.
Check your understanding. Why is splitting
maininto 3 to 5 methods better than writing one longmain, even when the long version compiles and works?Reveal answer
Three reasons. (1) Readability: a reader can grasp the program from
mainalone, then drill into the methods that interest them. (2) Reuse: ifletterGradeis its own method, you can call it for many scores without copying the if-chain. (3) Testing: small named methods can be tested in isolation; a 200-linemaincannot. The first reason alone is usually enough to justify the work.
Wrap up and what’s next
Recap.
- A method’s signature is name + parameter types + arity. Return type is not part of it.
- Two methods overload when their signatures differ. Two methods with the same signature are a duplicate-method compile error, even if their return types differ.
- Local variables (parameters and locals) live only inside the braces where they are declared. They cannot be reached from another method or after the closing brace.
- Decomposition is the skill of turning an English problem into 3 to 5 named verbs.
mainshould read like an outline, with the work delegated to small, well-named methods. - Overriding (same name, same signature, in a subclass) is a different idea, parked for Week 8.
What you can do now. Read a pair of method headers and decide overload or duplicate. Spot scope errors in CS1 code. Take a short English problem statement and sketch a 3 to 5 method decomposition before writing any code.
Next up: arrays. Arrays: A Row of Variables That Share a Name. The first data structure. One name, many slots, all the same type, indexed from zero. The first time pass-by-value tells a more interesting story (because the value being passed is a reference).
Related resources
- Reges & Stepp, Building Java Programs, Chapter 3 sections 3.4 and 3.5 cover overloading and the static-methods decomposition pattern.
- FAQ entries on duplicate-method errors and on the overloading-vs-overriding distinction.