Variable arguments (varargs)

A bill can have 2 items or 20. Instead of writing total(a, b), total(a, b, c) and so on, write one method total(int... prices) that accepts any number of arguments. You'll see that the three dots are just a friendly way of passing an array, and learn the two rules that keep varargs safe.

Module 5 · Methods⏱ 18 minBeginner8 practice problems
1 The story

How many overloads is enough?

After Lesson 5.4, Riya's bill calculator has total for 2, 3 and 4 items. Then a group orders 7 snacks at once. Writing total with 5, 6 and 7 parameters feels wrong.

total(10, 25) total(10, 25, 15, 30) total(10, 25, 15, 30, 20, 12, 8) total(int... prices) one method prices is an int[] myphatshala.in
Riya

Sir, do I really need a new overload for every number of items?

Arjun Sir

No. Write static int total(int... prices). Those three dots mean “zero or more ints”. Call it with 2 prices, 7 prices, or none at all.

Riya

But how do I loop over them if I don't know how many there are?

Arjun Sir

Inside the method, prices is simply an array. The compiler collects whatever you passed into a new int[]. So you use prices.length and a for-each loop, as in Lesson 4.1.

2 Definition

What are varargs?

Formal definition (write this in exams)

Varargs (variable-arity parameters), introduced in Java 5, let a method accept zero or more arguments of the same type. A varargs parameter is declared as Type... name; it must be the last parameter, and a method can have only one. Inside the method the parameter is an array of that type (Type[]). At each call site the compiler creates an array containing the trailing arguments; an existing array can also be passed directly.

In simple words

“Pass as many values as you like.” Java quietly puts them in an array and hands the method that array.

Words you'll meet in this lesson

VarargsA parameter written Type... name that accepts any number of values.
ArityThe number of arguments a method takes. Varargs methods have “variable arity”.
Fixed parametersThe normal parameters before the varargs one, e.g. String customer in bill(String customer, int... prices).
Implicit arrayThe array javac creates at the call site to hold the arguments.
Empty arrayWhat the method receives when called with no varargs values: length 0, not null.
Phase 3Varargs methods are tried last during overload resolution (Lesson 5.4).

The rules at a glance

Declaration or callValid?Why
int sum(int... n)YesOne varargs parameter, nothing after it
void bill(String name, int... prices)YesFixed parameters first, varargs last
void bad(int... a, String s)Novarargs parameter must be the last parameter
void two(int... a, int... b)NoOnly one varargs parameter, and it must be last
sum()YesThe method gets an empty array, n.length == 0
sum(new int[]{1, 2, 3})YesAn existing array is passed as it is

Exam tip: main can be written as public static void main(String... args); it means exactly the same as String[] args. Varargs is purely a compile-time convenience: in the class file the parameter is an ordinary array.

3 Types and ways to use it

Ways to use varargs

Only varargs

max(int... values). Fine when “none” has a sensible meaning, like a total of 0.

sum(), sum(5), sum(1, 2, 3)

Fixed parameters + varargs

bill(String customer, int... prices). Required values come first, extras after.

String.format(fmt, args...)

“At least one” with a fixed first value

max(int first, int... rest). The compiler now rejects max(), so there's no “empty” case to handle.

safer than checking length

Three ways to call the same method

public class Main {
    static int total(int... prices) {
        int sum = 0;
        for (int p : prices) {
            sum += p;
        }
        System.out.println(prices.length + " item(s): Rs " + sum);
        return sum;
    }

    public static void main(String[] args) {
        total();                          // no arguments → empty array
        total(40);                        // one
        total(10, 25, 15, 30);            // four
        int[] saved = {120, 80};
        total(saved);                     // an array you already have
    }
}
0 item(s): Rs 0 1 item(s): Rs 40 4 item(s): Rs 80 2 item(s): Rs 200

Varargs in the JDK

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        System.out.println(String.format("%s paid Rs %d via %s", "Riya", 60, "UPI"));
        System.out.printf("%-8s|%5d%n", "Tea", 10);
        List<String> menu = List.of("Tea", "Vada", "Samosa");
        System.out.println(menu + " " + Arrays.asList(3, 1, 2));
    }
}
Riya paid Rs 60 via UPI Tea | 10 [Tea, Vada, Samosa] [3, 1, 2]

Pitfall with null: total((int[]) null) passes a null array, and the for-each loop throws NullPointerException. A call with no arguments gives an empty array, never null, but a caller can still pass null explicitly. Public APIs often check for it.

4 Step by step

What javac does with a varargs call

  1. You declare the parameter with three dots

    static String bill(String customer, int... prices)
    In the class file this is just bill(String, int[]). The descriptor is (Ljava/lang/String;[I)Ljava/lang/String;, plus a flag that marks the method as varargs.
  2. You call it with loose values

    bill("Kabir", 10, 25, 15);
    The first argument fills the fixed parameter customer. All the remaining ones belong to the varargs parameter.
  3. javac rewrites the call to build an array

    bill("Kabir", new int[]{10, 25, 15});
    A new int[3] object is created on the heap at every call. For a call with no extra values, javac creates new int[0].
  4. The method works with the array

    for (int p : prices) sum += p;      // prices.length is 3
    Nothing special happens at run time: the JVM sees an ordinary method receiving an ordinary array reference.
  5. Overloads: varargs comes last

    static void log(String msg)            { … }   // chosen for log("x")
    static void log(String msg, Object... d) { … }   // only when extra values are passed
    During overload resolution, varargs methods are considered only in phase 3, after exact matches, widening and boxing. That's why libraries can add fast fixed-arity versions next to a varargs one.
5 See it happen

From loose values to an array

Left: the calls you write. Right: the array javac creates for each call and what the method sees.

bill(String customer, int... prices) 6 steps
What you see bill("Kabir", 10, 25, 15) bill("Kabir", 10, 25, 15) bill("Asha", 60) bill("Ravi") bill("Meena", saved) bill(10, 25, "Kabir") Kabir: 3 item(s), Rs 50 Asha: 1 item(s), Rs 60 Ravi: 0 item(s), Rs 0 Meena: 2 item(s), Rs 200 error: incompatible types: int cannot be converted to String (compiling…) “Kabir” fills customer; the rest are prices. Three prices added: 10 + 25 + 15. Just one price is fine too. No prices at all: still works. An int[] you already have is passed as it is. Fixed parameters must come first. myphatshala.in
Behind the screen What javac compiles, and the heap at run time bill("Kabir", new int[]{10, 25, 15}) bill("Asha", new int[]{60}) bill("Ravi", new int[0]) bill("Meena", saved) // no new array no match: (int, int, String) bill frame customer → String prices → 10 25 15 #1 int[3] (new) 60 #2 int[1] (new) empty #3 int[0] (new, not null) 120 80 saved's own array (shared) javac tries to match left to right: 10 → customer (String)? ✗ The call is rewritten to build an array. prices refers to a new int[3] on the heap. A new 1-element array for this call. A new empty array: the loop runs 0 times. The method gets a reference to saved's array. The class file is never written. descriptor: bill(Ljava/lang/String;[I)… myphatshala.in
6 Real-world use

Where varargs make APIs pleasant

Most programmers use varargs every day without noticing, through formatting, logging and collection helpers.

Watch it with an example: String.format fills a receipt

String.format(String format, Object... args) is a varargs method from the JDK. Watch the values you pass become an Object[], and each % placeholder take the next one.

String.format("%s paid Rs %d via %s", "Riya", 60, "UPI") 6 steps
Animated example "%s paid Rs %d via %s" args (Object[3], made by javac) "Riya" [0] 60 → Integer [1] "UPI" [2] Riya Riya paid Rs 60 Riya paid Rs 60 via UPI 1 method, any number of values the call has 4 arguments: format + 3 values myphatshala.in
"%s paid %d", name, amt

1. Formatting text: String.format and printf

String.format(String format, Object... args) takes any number of values to fill the placeholders: receipts, SMS templates, report lines.

Template + values→format(fmt, Object... args)→Finished message

2. Logging with placeholders

Logging libraries such as SLF4J offer log.info("Order {} paid by {}", id, user), with a varargs version for any number of values, and fixed one- and two-value versions for speed.

Message + details→info(String, Object...)→Log line

3. Building small collections quickly

List.of("Tea", "Vada"), Set.of(...), Arrays.asList(...) and EnumSet.of(...) accept a few values directly. List.of has fixed versions for up to 10 elements and a varargs version for more.

A few values→List.of(E... elements)→Unmodifiable list
max

4. Utility methods in your own code

Helpers like maxOf(int first, int... rest), joinPath(String... parts) or hasAnyRole(String... roles) read naturally at the call site.

hasAnyRole("ADMIN", "OPS")→String... roles→true / false

Examples are simplified. Varargs create a small array on every call, so performance-critical libraries often add fixed-arity overloads for the common cases.

7 Run the code

A canteen bill for any number of items

public class Main {
    static String bill(String customer, int... prices) {
        int sum = 0;
        for (int p : prices) {
            sum += p;
        }
        return customer + ": " + prices.length + " item(s), Rs " + sum;
    }

    public static void main(String[] args) {
        System.out.println(bill("Kabir", 10, 25, 15));
        System.out.println(bill("Asha", 60));
        System.out.println(bill("Ravi"));
        int[] saved = {120, 80};
        System.out.println(bill("Meena", saved));
    }
}
Kabir: 3 item(s), Rs 50 Asha: 1 item(s), Rs 60 Ravi: 0 item(s), Rs 0 Meena: 2 item(s), Rs 200
  • String customer, int... pricesFixed parameter first, the varargs parameter last.
  • for (int p : prices)prices is an int[], so a for-each loop works.
  • bill("Ravi")No prices: prices is an empty array, so the total is 0.
  • bill("Meena", saved)An existing array is accepted directly.
8 Line by line

A varargs call on the real JVM

Watch the heap: the call average(80, 90, 70) creates a new int[3], and the parameter marks refers to it.

Worked examples: real problems, solved and traced in memory

Three business tasks where the number of values changes from call to call. In each trace, look for the array that javac creates on the heap for the varargs parameter.

Example 1Food delivery · bills

Order total with a delivery fee

Problem statement

An order has a fixed delivery fee and any number of item prices. Write orderTotal(int deliveryFee, int... items) and print the total for an order of three items with a Rs 30 fee.

Input
delivery fee 30, items 120, 80, 45
Expected output
Pay Rs 275

Approach

  1. The fixed parameter (fee) comes first, the varargs (items) last.
  2. Start the total at the fee, then add every item.
  3. The call orderTotal(30, 120, 80, 45) creates an int[3] for the items.

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.

Example 2Analytics · ratings

Highest rating, at least one required

Problem statement

A product page shows the best rating. Write best(int first, int... rest) so that calling it without any rating is a compile error, and print the best of 4, 5, 3 and 5.

Input
ratings 4, 5, 3, 5
Expected output
Best rating: 5 stars

Approach

  1. Make the first rating a normal parameter: best() then won't compile.
  2. Start with first as the best so far.
  3. Loop over rest and keep the larger value.

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.

Example 3Security · access

Does the user have any of these roles?

Problem statement

An admin panel lets a user in if they have any of the allowed roles. Write hasAnyRole(String userRole, String... allowed) and check a user with role "OPS" against "ADMIN", "OPS" and "FINANCE".

Input
user role "OPS"; allowed: "ADMIN", "OPS", "FINANCE"
Expected output
Access granted: true

Approach

  1. Loop over allowed.
  2. Return true as soon as one matches with equals (Lesson 4.4).
  3. If the loop finishes, return false.

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.

9 Quiz and revision

Check yourself

Quick revision

Lesson 5.6 in one screen

  • Type... name accepts zero or more values; inside the method it is a Type[].
  • Only one varargs parameter per method, and it must be the last.
  • javac turns f(1, 2, 3) into f(new int[]{1, 2, 3}); f() gets new int[0].
  • Passing an existing array passes the same object (no copy). An explicit null is possible: guard public methods.
  • In overload resolution varargs come last (phase 3).
  • Use first, ...rest to require at least one value.
  • JDK examples: String.format, printf, List.of, Arrays.asList.

Interview and viva questions

What are varargs and how do they work internally?

A varargs parameter (Type... name) lets callers pass any number of arguments of that type. The compiler collects them into a new array at the call site and passes the array; inside the method the parameter is an ordinary array. In the class file the parameter type is Type[] and the method carries a varargs flag.

Why must varargs be the last parameter?

So the compiler can tell where the fixed arguments end: every argument after the fixed ones belongs to the varargs array. With a parameter after it, the boundary would be unclear, and allowing two varargs parameters would make it impossible.

What is the difference between f(int... a) and f(int[] a)?

Inside the method there is no difference. At the call site, f(int... a) also accepts loose values like f(1, 2, 3) and no values at all, while f(int[] a) requires an actual array argument.

Why does List.of have overloads for 0 to 10 elements as well as a varargs version?

Performance. Each varargs call allocates an array; fixed-arity overloads avoid that allocation for the common small cases, and the varargs version handles the rest.

What is @SafeVarargs?

An annotation for methods with a generic varargs parameter such as T... items. Generic arrays can cause “heap pollution” warnings; the annotation tells the compiler the method doesn't misuse the array. You'll meet it again with generics (Lesson 11.2).

Practice problems

Write varargs methods, predict overload choices, and avoid the null and empty-array traps. The last three are real business tasks.

P1Easy

Sum of any numbers

Write sum(int... n) and call it with no values, one value and five values.

Input
sum(), sum(7), sum(1, 2, 3, 4, 5)
Expected output
0
7
15
Show solution and explanation
public class Main {
    static int sum(int... n) {
        int total = 0;
        for (int x : n) total += x;
        return total;
    }

    public static void main(String[] args) {
        System.out.println(sum());
        System.out.println(sum(7));
        System.out.println(sum(1, 2, 3, 4, 5));
    }
}
0 7 15

How it works: with no values the method receives an empty array, so the loop doesn't run and 0 is returned.

P2Easy

Join words with a separator

Write join(String sep, String... words) that joins the words with the separator, using a StringBuilder.

Input
join(" | ", "Tea", "Vada", "Samosa"), join("-", "2025", "01", "26")
Expected output
Tea | Vada | Samosa
2025-01-26
Show solution and explanation
public class Main {
    static String join(String sep, String... words) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < words.length; i++) {
            if (i > 0) sb.append(sep);
            sb.append(words[i]);
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println(join(" | ", "Tea", "Vada", "Samosa"));
        System.out.println(join("-", "2025", "01", "26"));
    }
}
Tea | Vada | Samosa 2025-01-26

How it works: the separator is a fixed parameter and must come first. The index loop lets us skip the separator before the first word. (The JDK's String.join does the same job.)

P3Easy

Count values above a limit

Write countAbove(int limit, int... values) and count how many day temperatures were above 35.

Input
limit 35; temps 33, 36, 38, 34, 40
Expected output
Hot days: 3
Show solution and explanation
public class Main {
    static int countAbove(int limit, int... values) {
        int count = 0;
        for (int v : values) {
            if (v > limit) count++;
        }
        return count;
    }

    public static void main(String[] args) {
        System.out.println("Hot days: " + countAbove(35, 33, 36, 38, 34, 40));
    }
}
Hot days: 3

How it works: the first argument always fills limit; everything after it goes into the array.

P4Medium

Which overload wins?

Predict the output, then run. Methods: f(long), f(Integer), f(int...), and the calls f(5), f(5, 6), f().

Input
f(5), f(5, 6), f()
Expected output
long
varargs 2
varargs 0
Show solution and explanation
public class Main {
    static void f(long x)    { System.out.println("long"); }
    static void f(Integer x) { System.out.println("Integer"); }
    static void f(int... x)  { System.out.println("varargs " + x.length); }

    public static void main(String[] args) {
        f(5);
        f(5, 6);
        f();
    }
}
long varargs 2 varargs 0

How it works: f(5) is settled in phase 1 by widening to long. The other two calls have no fixed-arity match at all, so only phase 3 (varargs) can handle them.

P5Medium

Average without dividing by zero

Write average(int... marks) that returns 0.0 when no marks are given, instead of producing NaN.

Input
average(), average(80, 90, 70)
Expected output
0.0
80.0
Show solution and explanation
public class Main {
    static double average(int... marks) {
        if (marks.length == 0) return 0.0;
        int sum = 0;
        for (int m : marks) sum += m;
        return (double) sum / marks.length;
    }

    public static void main(String[] args) {
        System.out.println(average());
        System.out.println(average(80, 90, 70));
    }
}
0.0 80.0

How it works: without the check, 0.0 / 0 gives NaN (not a number), which then spreads through any calculation that uses it. Decide what “no values” means for your problem and handle it first.

P6Business problem

GST invoice for any number of lines

Write invoice(int gstPercent, int... amounts) that prints the subtotal, GST and grand total for a shop bill.

Input
GST 18%; amounts 250, 400, 150
Expected output
Items: 3
Subtotal: Rs 800
GST 18%: Rs 144
Total: Rs 944
Show solution and explanation
public class Main {
    static void invoice(int gstPercent, int... amounts) {
        int subtotal = 0;
        for (int a : amounts) subtotal += a;
        int gst = subtotal * gstPercent / 100;
        System.out.println("Items: " + amounts.length);
        System.out.println("Subtotal: Rs " + subtotal);
        System.out.println("GST " + gstPercent + "%: Rs " + gst);
        System.out.println("Total: Rs " + (subtotal + gst));
    }

    public static void main(String[] args) {
        invoice(18, 250, 400, 150);
    }
}
Items: 3 Subtotal: Rs 800 GST 18%: Rs 144 Total: Rs 944

How it works: the GST rate is required, so it's a fixed parameter; the line amounts vary per bill. 800 × 18 / 100 = 144.

P7Business problem

Send an alert to several phone numbers

Write alert(String message, String... phones) that prints one line per phone number and returns how many messages were sent. Phone numbers must be 10 digits; skip invalid ones.

Input
message "Exam hall changed to B-204"; phones "9876543210", "12345", "9123456789"
Expected output
SMS to 9876543210: Exam hall changed to B-204
skip invalid: 12345
SMS to 9123456789: Exam hall changed to B-204
2 message(s) sent
Show solution and explanation
public class Main {
    static int alert(String message, String... phones) {
        int sent = 0;
        for (String p : phones) {
            if (p.length() != 10) {
                System.out.println("skip invalid: " + p);
                continue;
            }
            System.out.println("SMS to " + p + ": " + message);
            sent++;
        }
        return sent;
    }

    public static void main(String[] args) {
        int n = alert("Exam hall changed to B-204", "9876543210", "12345", "9123456789");
        System.out.println(n + " message(s) sent");
    }
}
SMS to 9876543210: Exam hall changed to B-204 skip invalid: 12345 SMS to 9123456789: Exam hall changed to B-204 2 message(s) sent

How it works: the message is fixed; any number of recipients follow. continue (Lesson 3.5) skips bad numbers without stopping the loop.

P8Business problem

Cheapest delivery option

Several courier partners quote prices for a parcel. Write cheapest(String... quotes) where each quote looks like "Name:price", and return the cheapest one. Require at least one quote using a fixed first parameter.

Input
"FastShip:85", "QuickPost:70", "CityCourier:92"
Expected output
Book: QuickPost:70
Show solution and explanation
public class Main {
    static String cheapest(String first, String... more) {
        String best = first;
        for (String q : more) {
            if (price(q) < price(best)) best = q;
        }
        return best;
    }

    static int price(String quote) {
        return Integer.parseInt(quote.substring(quote.indexOf(':') + 1));
    }

    public static void main(String[] args) {
        System.out.println("Book: " + cheapest("FastShip:85", "QuickPost:70", "CityCourier:92"));
    }
}
Book: QuickPost:70

How it works: first, ...more makes an empty call impossible. A small helper method extracts the number after the colon, so the comparison stays readable.

Real-world scenario questions (asked in company interviews)

Varargs questions check whether you know what the three dots really compile to. Think of your answer first.

Platform · logging1–3 years

Why does the logger have info(String, Object) and info(String, Object...)?

You notice a logging API offers info(String msg, Object arg), info(String msg, Object a, Object b) and info(String msg, Object... args). Isn't the varargs one enough?

The interviewer is checking: do you know that each varargs call allocates an array?

Show a strong answer
  • Functionally yes, but every varargs call creates a new Object[], even when the log level is turned off and nothing is printed.
  • Logging is called millions of times; the fixed one- and two-argument versions avoid that garbage for the most common cases.
  • Overload resolution prefers fixed arity (phase 1) over varargs (phase 3), so callers get the fast version automatically.

Avoid: “varargs are free”. They are convenient, not free.

Fintech · API designFresher

max() of nothing

A teammate wrote static int max(int... values). Someone called max() and got ArrayIndexOutOfBoundsException from values[0]. How would you design it so the mistake can't happen?

The interviewer is checking: can you use the type system instead of run-time checks?

Show a strong answer
  • Change the signature to max(int first, int... rest). Now max() doesn't compile.
  • Start with first and loop over rest; the empty-array case needs no special code.
  • If the signature must stay, check values.length == 0 and throw IllegalArgumentException with a clear message.

Avoid: returning 0 for an empty list; it hides the bug.

E-commerce · bug hunt1–3 years

The discount that changed the saved cart

A method applyCoupon(int... prices) lowers prices[0] and returns the total. It was called with a cart's saved int[], and afterwards the cart itself showed the discounted price even though the order was cancelled. Explain.

The interviewer is checking: do you know that passing an array to varargs passes the same object?

Show a strong answer
  • An existing array is passed directly, not copied, so the method changed the cart's own array.
  • Fix: don't modify the parameter; work on a copy (prices.clone()) or compute the total without writing to the array.
  • Called with loose values, the bug would never show, because javac creates a fresh array each time. That's why it slipped through testing.

Avoid: assuming varargs always give the method its own private array.

Code reviewFresher

Varargs or a List parameter?

A method notifyUsers(String... userIds) is called from one place, with a list of 50,000 IDs loaded from the database: notifyUsers(ids.toArray(new String[0])). Is varargs the right choice here?

The interviewer is checking: do you know when varargs help and when they don't?

Show a strong answer
  • Varargs shine when callers type a few values by hand. Here the data is already a collection, so the conversion to an array is extra work and noise.
  • A List<String> (or Collection<String>) parameter states the intent better and avoids copying 50,000 references.
  • If both styles are needed, offer both, with the varargs version calling the List version via List.of(ids).

Avoid: using varargs everywhere just because it looks flexible.