☕ Java
varargs
Varargs (variable-length arguments) allow a method to accept any number of arguments of a specified type — including zero arguments. The varargs parameter is declared with an ellipsis (...) after the type and must be the last parameter in the parameter list. Inside the method, the varargs parameter is treated as a regular array. Varargs eliminate the need for method overloading just to handle different argument counts and allow callers to pass values directly without manually constructing an array.
What Varargs Are and How They Work
Varargs — short for variable-length arguments — were introduced in Java 5 to solve the problem of methods that accept a variable number of arguments of the same type. Before varargs, you either needed multiple overloads (one for one argument, one for two, one for three...) or you required the caller to explicitly create an array and pass it. Varargs allow the caller to pass any number of arguments directly and let the compiler handle the array creation transparently.
The varargs syntax places an ellipsis (...) between the element type and the parameter name in the method declaration. At the call site, the caller can pass any number of comma-separated values of the compatible type, including zero. The compiler collects these values, creates an array containing them, and passes the array to the method. Inside the method, the varargs parameter is a regular array with all the operations and properties of a normal array.
The key rule is that varargs must be the last parameter in the method signature. A method can have any number of fixed parameters followed by exactly one varargs parameter. This constraint exists because the compiler needs to know where the fixed arguments end and the variable arguments begin. A method with two varargs parameters would be ambiguous — the compiler could not determine which arguments belong to which parameter.
Understanding that varargs is syntactic sugar for array passing is crucial. The method printf(String format, Object... args) is equivalent to printf(String format, Object[] args) — the only difference is the call syntax. Internally the method receives an Object array. This also means you can pass an existing array directly to a varargs parameter — the array is passed as-is without wrapping.
Java
// ── Varargs declaration and basic usage: ─────────────────────────────
public static int sum(int... numbers) {
// 'numbers' is a regular int[] inside the method
int total = 0;
for (int n : numbers) total += n;
return total;
}
// ── Call with any number of arguments: ───────────────────────────────
System.out.println(sum()); // 0 — zero args, empty array
System.out.println(sum(5)); // 5 — one arg
System.out.println(sum(1, 2, 3)); // 6 — three args
System.out.println(sum(1, 2, 3, 4, 5)); // 15 — five args
// ── Pass an existing array directly: ─────────────────────────────────
int[] existing = {10, 20, 30};
System.out.println(sum(existing)); // 60 — array passed directly
// ── What the compiler generates: ─────────────────────────────────────
// sum(1, 2, 3) is compiled as: sum(new int[]{1, 2, 3})
// sum() is compiled as: sum(new int[]{}) (empty array, NOT null)
// sum(existing) passes existing directly — no new array created
// ── Varargs with other parameters — varargs must be last: ─────────────
public static double weightedAverage(double weight, int... scores) {
if (scores.length == 0) return 0.0;
double sum = 0;
for (int score : scores) sum += score;
return (sum / scores.length) * weight;
}
System.out.println(weightedAverage(1.0, 90, 85, 92)); // 89.0
System.out.println(weightedAverage(0.5, 70, 80, 60)); // 35.0
// ── Multiple fixed params followed by varargs: ────────────────────────
public static void log(String level, String format, Object... args) {
String message = String.format(format, args);
System.out.printf("[%s] %s%n", level, message);
}
log("INFO", "User %s logged in from %s", "Alice", "192.168.1.1");
log("ERROR", "Order %d failed: %s", 1001, "Payment declined");
log("DEBUG", "Starting service"); // zero varargsVarargs vs Array Parameters
Varargs and explicit array parameters are closely related — a varargs parameter is an array inside the method. The difference is entirely in the call syntax: varargs allows passing multiple values separated by commas, while an array parameter requires the caller to explicitly create and pass an array. Both can also accept an existing array reference.
The choice between varargs and an explicit array parameter is a question of API design — specifically, what is most natural and convenient for the caller. Varargs is appropriate when the caller will typically pass individual values rather than a pre-built array, when zero arguments should be a valid call, and when the method is used frequently enough that the convenience matters. An explicit array parameter is appropriate when the data already exists as an array and passing individual values would be unnatural, when the method is primarily called with array data from other operations, or when you want to prevent a zero-argument call by requiring a non-empty array (which varargs cannot enforce at compile time).
A critical difference is how null behaves. Passing null to a varargs parameter is ambiguous: is null a single element that should produce a one-element null-containing array, or is it the array reference itself (a null array)? The compiler treats null as the array reference — it passes null directly as the array, not wrapped in an array. This means the varargs parameter is null inside the method, and any access to its length or elements throws NullPointerException. Always check for null when a varargs method may be called with null, and document whether null is a valid argument.
Java
// ── Varargs vs array parameter — API comparison: ─────────────────────
// Varargs — caller passes values directly:
public static double average(double... values) {
if (values == null || values.length == 0) return 0.0;
double sum = 0;
for (double v : values) sum += v;
return sum / values.length;
}
// Explicit array — caller must construct the array:
public static double averageArray(double[] values) {
if (values == null || values.length == 0) return 0.0;
double sum = 0;
for (double v : values) sum += v;
return sum / values.length;
}
// ── Calling each: ─────────────────────────────────────────────────────
// Varargs — natural for individual values:
average(90.0, 85.0, 92.0); // ✓ clean
average(); // ✓ zero args works
// Array — requires explicit array construction:
averageArray(new double[]{90.0, 85.0, 92.0}); // verbose
// averageArray(90.0, 85.0, 92.0); // COMPILE ERROR
// Passing an existing array — both work:
double[] data = {90.0, 85.0, 92.0};
average(data); // ✓ passes array directly to varargs
averageArray(data); // ✓ same array parameter
// ── The null problem with varargs: ────────────────────────────────────
public static void printAll(String... items) {
if (items == null) {
System.out.println("null array received");
return;
}
for (String item : items) {
System.out.println(item);
}
}
printAll("a", "b", "c"); // prints a, b, c
printAll(); // prints nothing (empty array)
printAll(null); // items IS null — must null-check!
// To pass a single null String element (not a null array):
printAll((String) null); // cast forces wrapping in array: items = [null]
printAll(new String[]{null}); // explicit — same result
// ── Varargs performance consideration: ────────────────────────────────
// Every varargs call creates a new array:
sum(1, 2, 3); // compiles to: sum(new int[]{1, 2, 3}) — allocation!
// For extremely performance-sensitive hot paths, overloads prevent allocation:
public static int sum(int a) { return a; }
public static int sum(int a, int b) { return a + b; }
public static int sum(int a, int b, int c) { return a + b + c; }
public static int sum(int... rest) { /*fallback*/ int t=0; for(int n:rest)t+=n; return t; }
// Used by Java's EnumSet and other standard library methods for this reason.Varargs Overloading and Ambiguity
Varargs methods can be overloaded, but mixing varargs with overloaded methods can create ambiguity. The compiler resolves overloads in three phases, and varargs methods are only considered in the third phase — after methods without varargs have been tried. This means a non-varargs overload always wins over a varargs overload when both could apply, which is the desired behaviour in most cases.
Ambiguity arises when two varargs methods are both applicable to a call and neither is more specific. Calling max() where max(int... values) and max(double... values) both exist is not ambiguous because int is not a subtype of double in the varargs sense and vice versa. But calling a method with no arguments when there are two varargs overloads that both accept zero arguments produces a compile error.
The most important practical guideline is to avoid designing APIs where callers might be confused about which overload is selected. When a method has both a varargs overload and a specific-count overload, the specific-count overload is preferred by the compiler for exact-count calls — but the caller may not realise this and may be surprised by which implementation runs. Document the overloading strategy clearly, or prefer a single varargs method with runtime checks over a confusing overload set.
Java
// ── Overload resolution — non-varargs wins over varargs: ─────────────
public class OverloadExample {
// Phase 1 (preferred): exact match, no conversion:
public static String describe(int a, int b) {
return "two ints: " + a + ", " + b;
}
// Phase 3 (last resort): varargs:
public static String describe(int... nums) {
return "varargs: " + Arrays.toString(nums);
}
}
System.out.println(OverloadExample.describe(1, 2));
// "two ints: 1, 2" — non-varargs wins
System.out.println(OverloadExample.describe(1, 2, 3));
// "varargs: [1, 2, 3]" — no non-varargs match, falls through to varargs
System.out.println(OverloadExample.describe(1));
// "varargs: [1]" — no non-varargs match for single int
// ── Ambiguity — two equally applicable varargs: ───────────────────────
public class Ambiguous {
public static void print(Object... objs) { System.out.println("Object"); }
public static void print(String... strs) { System.out.println("String"); }
}
// Ambiguous.print("hello"); // COMPILE ERROR — both are applicable
// String is more specific, but Java 8+ resolves this
// String overload wins (String is subtype of Object)
Ambiguous.print("hello"); // "String" — String[] is more specific than Object[]
Ambiguous.print(new Object()); // "Object" — not a String
// ── Safe varargs annotation: ──────────────────────────────────────────
// @SafeVarargs suppresses unchecked warnings for generic varargs methods.
// Used when the method does not perform unsafe operations on the varargs array.
@SafeVarargs
public static <T> List<T> listOf(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
List<String> names = listOf("Alice", "Bob", "Charlie");
List<Integer> nums = listOf(1, 2, 3, 4, 5);
System.out.println(names); // [Alice, Bob, Charlie]
System.out.println(nums); // [1, 2, 3, 4, 5]
// ── Common real-world varargs APIs in Java standard library: ──────────
// String.format(String format, Object... args)
// System.out.printf(String format, Object... args)
// List.of(E... elements) — Java 9+
// Arrays.asList(T... a)
// Collections.addAll(Collection<T> c, T... elements)
// Objects.hash(Object... values) — compute hash from multiple fieldsRelated Topics in Arrays
Array Basics
An array in Java is a fixed-size, ordered collection of elements of the same type stored in a contiguous block of memory. Once created, the size of an array cannot change. Arrays are the simplest and most fundamental data structure in Java — they underlie many higher-level collections and provide direct, indexed access to elements in constant time O(1). Every array in Java is an object, inherits from java.lang.Object, and can hold either primitives or references.
One-Dimensional Array
A one-dimensional array is a linear sequence of elements of the same type, accessed by a single integer index. It is the simplest array form in Java and the foundation for understanding multidimensional arrays and collection classes. One-dimensional arrays are used to store and process lists of values — student grades, product prices, daily temperatures, character sequences — any time you need to work with a fixed-size ordered collection of homogeneous data.
Two-Dimensional Array
A two-dimensional array in Java is an array of arrays — each element of the outer array is itself a one-dimensional array. It is used to represent tabular data with rows and columns: matrices, game boards, spreadsheet grids, pixel buffers, and any data that is naturally organised in two dimensions. In Java, a 2D array is declared with two sets of brackets and accessed with two indices: array[row][column].
Multidimensional Array
A multidimensional array in Java is an array with more than two dimensions — an array of arrays of arrays, and so on. While two-dimensional arrays suffice for most programming tasks, three-dimensional arrays are used for spatial data (x, y, z coordinates of a 3D grid), volumetric data (layers, rows, columns), RGB pixel buffers, and scientific computations involving tensors. Java supports arbitrary dimensionality, though arrays beyond three dimensions are rare in practice.