Three names for one idea
Riya's canteen app adds up a bill. She has written totalOfTwo(a, b), totalOfThree(a, b, c) and totalWithDiscount(a, b, percent). Her friend Kabir keeps calling the wrong one, and every new case needs a new name.
Sir, I keep inventing names: totalOfTwo, totalOfThree… Is there a better way?
Give them all the same name, total. Java allows several methods with one name as long as their parameter lists differ. The compiler looks at the arguments in each call and picks the matching version.
So when I write total(40, 25, 15), Java decides at run time which one to use?
No, earlier! The compiler decides, while compiling. By the time the program runs, each call already points to one exact method. You use this every day: println has ten versions.
What is method overloading?
Method overloading means defining two or more methods in the same class with the same name but different parameter lists: a different number of parameters, different parameter types, or a different order of types. The method's signature (its name plus its parameter types) must be unique; the return type and parameter names are not part of it. The compiler chooses which overloaded method to call from the static types of the arguments, so overloading is a form of compile-time (static) polymorphism.
Same name, different inputs. You call total(…); Java looks at what you passed in and runs the version written for exactly those inputs.
Words you'll meet in this lesson
total(int, int). Must be unique in a class.What counts as a different parameter list?
| Change | Example pair | Valid overload? |
|---|---|---|
| Number of parameters | area(int) and area(int, int) | Yes |
| Type of parameters | area(int) and area(double) | Yes |
| Order of parameter types | pay(int, String) and pay(String, int) | Yes (but confusing; avoid) |
| Only the return type | int calc(int) and double calc(int) | No: method calc(int) is already defined |
| Only parameter names | fee(int days) and fee(int hours) | No: same signature |
Only static or access modifier | static void log(String) and void log(String) | No: same signature |
Exam tip: “Can we overload by return type?” No. A call like calc(5); doesn't have to use the result, so the compiler couldn't tell the two apart. Overloading is compile-time polymorphism; overriding (Lesson 7.4) is run-time polymorphism.
Three ways to overload, and where you've already seen them
By number of parameters
total(a, b) and total(a, b, c). The most common kind, often used for optional extras.
By type of parameters
print(int), print(double), print(String). Same idea, different kinds of data.
By order of types
book(int seats, String show) and book(String show, int seats). Legal, but easy to misuse: prefer different names.
The JDK is full of overloads
public class Main {
public static void main(String[] args) {
System.out.println(42); // println(int)
System.out.println(4.5); // println(double)
System.out.println('A'); // println(char)
System.out.println("Chai"); // println(String)
System.out.println(Math.abs(-7)); // abs(int) returns int
System.out.println(Math.abs(-7.5)); // abs(double) returns double
System.out.println(String.valueOf(true) + String.valueOf(99));
}
}Each line calls a different method that happens to share a name. Notice that the two Math.abs methods also return different types. That's allowed, because their parameters differ too.
Constructors can be overloaded too
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> a = new ArrayList<>(); // ArrayList()
ArrayList<String> b = new ArrayList<>(500); // ArrayList(int initialCapacity)
StringBuilder s1 = new StringBuilder(); // StringBuilder()
StringBuilder s2 = new StringBuilder("Bill: "); // StringBuilder(String)
System.out.println(a.size() + " " + b.size() + " [" + s1 + "] [" + s2 + "]");
}
}Good practice: overloads should all do the same job. A common pattern is for the short versions to call the full one: total(a, b) just returns total(a, b, 0). Then the real logic lives in one place.
How javac chooses one method for a call
Collect every method with that name
javac finds the threestatic void show(long x) { System.out.println("long"); } static void show(Integer x) { System.out.println("Integer"); } static void show(int... x) { System.out.println("varargs"); } show(5); // which one?showmethods in the class. Methods with a different name are never considered.Phase 1: exact match or widening only
The argument5is anint. Noshow(int)exists, butintcan widen tolong. Soshow(long)is applicable in phase 1, and javac stops here: it prints long. Boxing and varargs are not even tried.Phase 2: allow boxing and unboxing
Only if phase 1 finds nothing. Removeshow(long)and the call boxes5into anInteger: it prints Integer.Phase 3: allow varargs
Last resort. Remove the first two andshow(int...)is used: the JVM receives a newint[]{5}. Varargs come in Lesson 5.6.Several fit in the same phase? Pick the most specific, or fail
If one candidate's parameters are all “narrower” than another's, it wins (Main.java:6: error: reference to add is ambiguous add(1, 1); ^ both method add(int,long) in Main and method add(long,int) in Main matchStringbeatsObject;intbeatslong). Here each method is better for one argument and worse for the other, so neither is more specific: compile error.The choice is written into the bytecode
Each call instruction names the method and its descriptor:invokestatic #33 // Method area:(I)I invokestatic #42 // Method area:(II)I invokestatic #47 // Method area:(D)D(I)Imeans “takes an int, returns an int”,(D)Ddouble to double. The JVM never has to choose; javac already did.
Three calls, three different methods
Left: the call you write and what it prints. Right: how javac matches the arguments to a signature and what it writes into the class file.
area(5)matchesarea(int)exactly and prints 25.- With two int arguments only
area(int, int)has the right number of parameters: 4 × 6 = 24. 2.5is a double, soarea(double)runs: 3.14 × 2.5 × 2.5 = 19.625.- Surprise: a
charis a number (its Unicode value). 'A' is 65, which widens to int, soarea(int)prints 4225. - A
longcannot go into anintparameter without a cast, but it can widen todouble, soarea(double)is chosen: 3.14 × 5.0 × 5.0 = 78.5. - Two methods that differ only by return type have the same signature,
calc(int). That's a compile error.
- javac lists the argument types, here just
(int), and checks everyarea. Two are applicable;area(int)is more specific thanarea(double), so it wins. The call is compiled asinvokestatic area:(I)I. - The number of arguments rules out the one-parameter versions immediately.
- A double can't be passed to an int parameter (that would lose the fraction), so only
area(double)applies. Descriptor(D)D. - char can widen to int (and to double). The int version is more specific. In the bytecode a char is already held as an int, so no conversion instruction is needed.
- long → int is narrowing (not automatic); long → double is widening. javac picks
area(double)and widens the argument. For the literal5Lit simply stores 5.0; for a long variable it would insert anl2d(long to double) instruction. - The rule check happens before any bytecode exists. Because both methods would have the signature
calc(int), the class can't be compiled.
Where overloading shows up in real code
Overloading is how good APIs stay easy to use: one name to remember, and sensible defaults for the details you don't care about.
Watch it with an example: one pay() name, three ways to pay
A canteen app has three pay methods. Each call below has different arguments, so javac connects it to a different method before the app ever runs.
- pay(60): one int argument. Only
pay(int)has one parameter, so this becomes a cash payment. - pay(60, "canteen@upi"): an int and a String: exactly the UPI version.
- pay(60, "4111…", 123): int, String, int: the card version. Three calls, one name, three different methods.
- pay("60"): a String where every version expects an int first. No overload fits, so the code doesn't compile. The mistake is caught before any money moves.
- pay(60, 123): two ints. The two-parameter version wants a String second, and an int can't become a String automatically. Compile error again.
1. Printing and converting: println, valueOf
PrintStream.println has versions for every primitive, char[], String and Object. String.valueOf works the same way. You write one name; the right conversion happens.
2. Optional details with defaults
Logging libraries offer log(message) and log(message, exception). HTTP clients offer get(url) and get(url, headers). The short versions call the long one with default values.
3. Different ways to build an object
Constructors are overloaded constantly: new ArrayList<>(), new ArrayList<>(500), new ArrayList<>(otherList). A booking class might accept a seat number or a seat code.
4. Same operation, different data types
Math.max, Math.abs and Arrays.sort each have versions for int, long, float, double (and more), so each type gets fast code without boxing.
Examples are simplified. Library designers keep overload sets small and consistent; when two overloads would behave differently, they choose different method names instead.
Riya's bill calculator with overloads
public class Main {
static int total(int a, int b) {
return total(a, b, 0);
}
static int total(int a, int b, int c) {
return a + b + c;
}
static double total(int a, int b, double discount) {
return (a + b) * (1 - discount);
}
public static void main(String[] args) {
System.out.println("Tea + Vada: " + total(10, 25));
System.out.println("Tea + Vada + Samosa: " + total(10, 25, 15));
System.out.println("With 10% off: " + total(40, 60, 0.10));
}
}total(int a, int b)The short version doesn't repeat the logic: it calls the three-int version with 0.total(int, int, int)vstotal(int, int, double)Same count, different type of the third parameter: a valid overload.total(10, 25, 15)15 is an int, so the exact match(int, int, int)wins over widening to double.total(40, 60, 0.10)0.10 is a double, so only the discount version applies. It returns a double: 90.0.
One overload calling another
The two-argument total delegates to the three-argument one. Watch two frames with the same method name sit on the stack at the same time.
Worked examples: real problems, solved and traced in memory
Three everyday problems where overloading keeps the code short. In each one, the simple versions delegate to a full version, so the real rule is written once. Run each solution, then step through it.
Fare with and without surge
Problem statement
A ride app charges Rs 12 per km. At peak time it multiplies the fare by a surge factor. Write fare(int km) and fare(int km, double surge), where the first simply calls the second with a surge of 1.0. Print the fare for a 10 km ride at normal time and at 1.5× surge.
10 km normal, 10 km at 1.5x surge
Normal: Rs 120.0 Peak: Rs 180.0
Approach
- Write the full rule once in
fare(int km, double surge):km * 12 * surge. fare(int km)returnsfare(km, 1.0).- javac picks the version from the number of arguments in each call.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Area of a room, a plot and a circular tank
Problem statement
A builder's estimator needs areas: a square room (one side), a rectangular plot (length and width) and a circular water tank (radius, a double). Write three overloads named area and print each result.
square side 4, plot 12 x 9, tank radius 1.5
Room: 16 sq ft Plot: 108 sq ft Tank: 7.0649999999999995 sq m
Approach
area(int side)callsarea(side, side): a square is a rectangle.area(int l, int w)returnsl * w.area(double r)uses 3.14 × r × r. The double argument selects it.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Invoice lines for one item or many
Problem statement
A shop prints invoice lines. line(String item, int price) is for a single item; line(String item, int price, int qty) is for several. Both must print the same format, so the single-item version calls the other with qty 1.
Pen Rs 10 (1 piece), Notebook Rs 45 x 3
Pen x1 = Rs 10 Notebook x3 = Rs 135
Approach
- Write the formatting once in the three-parameter version.
- The two-parameter version passes qty = 1.
- main calls each version once.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 5.4 in one screen
- Overloading = same method name, different parameter lists (number, type or order of types) in one class.
- Signature = name + parameter types. Return type, parameter names, static and access modifiers don't count.
- javac chooses at compile time: phase 1 exact/widening → phase 2 boxing → phase 3 varargs; then the most specific method.
- No single most specific method → reference to … is ambiguous. Same signature twice → already defined.
- The choice is stored in the bytecode as a descriptor like
area:(I)I. - Good overloads all do the same job; short versions delegate to the full one.
- Constructors and
maincan be overloaded too.
Interview and viva questions
What is method overloading? Give an example from the JDK.
Defining several methods with the same name but different parameter lists in one class. The compiler picks one based on the argument types. System.out.println is the classic example: there are versions for int, long, double, char, boolean, char[], String and Object.
Why can't we overload by return type alone?
Because a call doesn't always use its result (calc(5); is a valid statement), the compiler would have no way to choose. Java therefore defines the signature as name plus parameter types only, and two methods with the same signature are a compile error.
What is the difference between overloading and overriding?
Overloading: same name, different parameters, usually in the same class, chosen by the compiler (static polymorphism). Overriding: a subclass gives a new body for a method with the same signature as in the parent, chosen at run time from the object's actual class (dynamic polymorphism).
In what order does Java try widening, boxing and varargs?
First only exact matches and widening, then boxing/unboxing, and varargs only as the last resort. Within a phase, the most specific method wins. This order keeps old code (written before boxing and varargs existed in Java 5) behaving the same way.
Can static methods be overloaded? Can main be overloaded?
Yes to both. Overloading works for static and instance methods alike. You can write main(int x), but the JVM only starts a program from public static void main(String[] args).
Practice problems
Write overloads, predict which one javac chooses, and fix the classic mistakes. The last three are real business tasks.
max of two or three
Write max(int a, int b) and max(int a, int b, int c). The three-argument version must reuse the two-argument one.
max(4, 9), max(7, 2, 5)
9 7
Show solution and explanation
public class Main {
static int max(int a, int b) {
return a > b ? a : b;
}
static int max(int a, int b, int c) {
return max(max(a, b), c);
}
public static void main(String[] args) {
System.out.println(max(4, 9));
System.out.println(max(7, 2, 5));
}
}How it works: the calls differ in the number of arguments, so javac picks each version from the count alone. Reusing max(a, b) keeps the comparison logic in one place.
Describe any value
Write three overloads of describe for an int, a double and a String, each printing the type and the value.
describe(42), describe(3.5), describe("UPI")int: 42 double: 3.5 String: UPI (3 chars)
Show solution and explanation
public class Main {
static void describe(int x) { System.out.println("int: " + x); }
static void describe(double x) { System.out.println("double: " + x); }
static void describe(String x) { System.out.println("String: " + x + " (" + x.length() + " chars)"); }
public static void main(String[] args) {
describe(42);
describe(3.5);
describe("UPI");
}
}How it works: overloading by parameter type. This is exactly how println handles different types.
Predict the choice
Given show(int) and show(double), predict what each call prints: a byte, a char, a long and a float.
show((byte) 3), show('C'), show(10L), show(2.5f)3 -> int 67 -> int 10.0 -> double 2.5 -> double
Show solution and explanation
public class Main {
static void show(int x) { System.out.println(x + " -> int"); }
static void show(double x) { System.out.println(x + " -> double"); }
public static void main(String[] args) {
show((byte) 3);
show('C');
show(10L);
show(2.5f);
}
}How it works: byte and char widen to int, and the int version is more specific than double. A long can't narrow to int, and a float can't either, but both widen to double. Note that 'C' prints as 67 inside the int version.
Widening, boxing or varargs?
Three overloads exist: pick(long), pick(Integer) and pick(int...). Show which one a call with an int uses, then what happens with an Integer object and with two ints.
pick(5), pick(Integer.valueOf(5)), pick(5, 6)
long Integer varargs (2 values)
Show solution and explanation
public class Main {
static void pick(long x) { System.out.println("long"); }
static void pick(Integer x) { System.out.println("Integer"); }
static void pick(int... x) { System.out.println("varargs (" + x.length + " values)"); }
public static void main(String[] args) {
pick(5);
pick(Integer.valueOf(5));
pick(5, 6);
}
}How it works: pick(5): phase 1 (widening) already finds pick(long). pick(Integer.valueOf(5)): an Integer matches pick(Integer) exactly in phase 1. pick(5, 6): nothing takes two parameters, so only phase 3 (varargs) works.
Fix: overload by return type
This code doesn't compile: int convert(int x) and double convert(int x). Rewrite it so one method converts paise to rupees (returns double) and the other doubles an amount, with names or parameters that make the two clearly different.
250 paise, Rs 40 doubled
2.5 80
Show solution and explanation
public class Main {
static double toRupees(int paise) {
return paise / 100.0;
}
static int doubled(int amount) {
return amount * 2;
}
public static void main(String[] args) {
System.out.println(toRupees(250));
System.out.println(doubled(40));
}
}How it works: two methods with signature convert(int) can't exist together, whatever their return types. Because they do different jobs, the right fix is two descriptive names, not an artificial parameter change.
Shipping cost with optional express
An online store charges Rs 40 plus Rs 10 per kg. Express delivery adds Rs 60. Write shipping(int kg) and shipping(int kg, boolean express); the first calls the second with false.
2 kg normal, 2 kg express, 5 kg normal
2 kg: Rs 60 2 kg express: Rs 120 5 kg: Rs 90
Show solution and explanation
public class Main {
static int shipping(int kg) {
return shipping(kg, false);
}
static int shipping(int kg, boolean express) {
int cost = 40 + 10 * kg;
if (express) {
cost += 60;
}
return cost;
}
public static void main(String[] args) {
System.out.println("2 kg: Rs " + shipping(2));
System.out.println("2 kg express: Rs " + shipping(2, true));
System.out.println("5 kg: Rs " + shipping(5));
}
}How it works: the optional detail (express) has a sensible default. Most callers use the short version; the pricing rule exists only once.
Masking a phone number or an account number
A bank's SMS shows only the last 4 digits. Write mask(long phone) and mask(String accountNo); the long version converts to a String and calls the String version.
phone 9876543210, account "001234567890"
XXXXXX3210 XXXXXXXX7890
Show solution and explanation
public class Main {
static String mask(String accountNo) {
int keep = 4;
String last = accountNo.substring(accountNo.length() - keep);
return "X".repeat(accountNo.length() - keep) + last;
}
static String mask(long phone) {
return mask(String.valueOf(phone));
}
public static void main(String[] args) {
System.out.println(mask(9876543210L));
System.out.println(mask("001234567890"));
}
}How it works: the phone number needs L because 9876543210 is too big for an int. The long overload converts once and reuses the masking logic. (String.repeat needs Java 11+.)
Simple interest with a default rate
A loan calculator uses 9% per year unless a rate is given. Write interest(int principal, int years) and interest(int principal, int years, double rate), and print both results for Rs 50,000 over 2 years (default rate and 7.5%).
50000 for 2 years; at 9% and at 7.5%
Default 9%: Rs 9000.0 At 7.5%: Rs 7500.0
Show solution and explanation
public class Main {
static final double DEFAULT_RATE = 9.0;
static double interest(int principal, int years) {
return interest(principal, years, DEFAULT_RATE);
}
static double interest(int principal, int years, double rate) {
return principal * rate * years / 100;
}
public static void main(String[] args) {
System.out.println("Default 9%: Rs " + interest(50000, 2));
System.out.println("At 7.5%: Rs " + interest(50000, 2, 7.5));
}
}How it works: simple interest = P × R × T / 100. The default rate lives in a named constant, and the two-argument version passes it on, so changing the default needs one edit.
Real-world scenario questions (asked in company interviews)
Interviewers like overloading because it tests whether you know what the compiler does, not just the syntax. Think of your answer first, then open the model answer.
“Can I add a version that returns a receipt?”
A PaymentService has void pay(int amount, String upiId). A teammate wants a version that returns a Receipt and writes Receipt pay(int amount, String upiId) next to it. It won't compile. Explain and suggest a fix.
The interviewer is checking: do you know that the return type is not part of the signature?
Show a strong answer
- Both methods have the signature
pay(int, String). Return type doesn't count, so javac reports method pay(int,String) is already defined. - Either change the existing method to return the Receipt (callers can ignore it), or give the new one a clear name like
payAndGetReceipt. - Don't “fix” it by swapping parameter order;
pay(String, int)compiles but invites mistakes.
Avoid: saying the JVM will choose based on how the result is used.
The discount that became an extra item
A cart has total(int a, int b, int extraItem) and total(int a, int b, double discount). A developer meant to give 10% off and wrote total(400, 600, 10). Customers were charged Rs 1010 instead of Rs 900. Why, and how would you prevent it?
The interviewer is checking: do you understand overload resolution and API design risks?
Show a strong answer
10is an int, so the exact matchtotal(int, int, int)is chosen at compile time: 400 + 600 + 10 = 1010.- The overloads do different jobs under one name. Rename them:
totalWithExtraandtotalWithDiscount, or use aDiscounttype so the compiler catches mistakes. - Add a unit test for the discount path; this bug passes compilation silently.
Avoid: overloads whose meaning changes with the parameter type.
Adding an overload broke someone's build
Your shared library had send(String userId, Object payload). You added send(String userId, String message). Another team's code send(id, null) still compiles, but now behaves differently. What happened?
The interviewer is checking: do you know that overload choice is fixed at compile time and depends on “most specific”?
Show a strong answer
- When they recompiled,
nullfits both, andStringis more specific thanObject, so javac now picks the new method. - Code compiled against the old version keeps calling the old method until it is recompiled, because the descriptor is stored in their class files.
- Adding overloads to a public API is a compatibility risk; document it, or use a new method name.
Avoid: assuming that adding a method can never change existing behaviour.
Why does Math.max have four versions?
A junior asks why the JDK has max(int,int), max(long,long), max(float,float) and max(double,double) instead of one max(Number, Number). What do you say?
The interviewer is checking: can you connect overloading to boxing costs and type safety?
Show a strong answer
- Primitive overloads avoid boxing: no
Integerobjects are created in hot loops, and the result keeps its exact type. - Each version uses the right comparison for its type (for doubles, special handling of NaN and −0.0).
- Callers still write just
Math.max(a, b); the compiler picks the fastest matching version.
Avoid: “it's just old code”. It is a deliberate performance and correctness choice.