Swift & Objective-C

Does a method signature include the return type?

When asked, “What is a method signature?”, most people hedge with “The method’s name and parameters… something like that.”

6 min read
Cover image for Does a method signature include the return type?

When asked, “What is a method signature?”, most people hedge with “The method’s name and parameters… something like that.”

It’s not wrong, but one step deeper draws a clear line. “Then, is the return type part of the signature?” That’s where answers start to waver. This is a common technical-interview question and a key criterion for whether overloading is allowed.

More interestingly, the answer differs by language. In Java, the return type is not part of the signature, while Swift allows overloading based only on the return type.

This article covers the precise definition of a signature, language-specific differences, and why signatures matter in practice.


A signature is a method’s ID

A method signature is, in short, the identifying information a compiler uses to distinguish this method from others.

Think of it as an ID card. A name alone cannot distinguish people with the same name, so an ID includes additional information such as a date of birth. Methods work the same way. Multiple methods can share a name (overloading), so other information must identify exactly one of them.

In general, a signature consists of these elements.

  • Method name
  • Parameter types
  • Number of parameters
  • Parameter order
// All four of these methods have different signatures
void print(int value)              // print(int)
void print(String value)           // print(String) — Different types
void print(int a, int b)           // print(int, int) — Different counts
void print(String s, int n)        // print(String, int)
void print(int n, String s)        // print(int, String) — Different order

One thing to note: parameter names are not part of the signature. print(int value) and print(int number) have the same signature. From the compiler’s perspective, there is no way to distinguish them by looking only at print(3) in the call site.

Access modifiers (public, private), static, final, and exception declarations (throws) are not part of the signature either. A signature contains only the information needed to determine “which method to call.”


So, does it include the return type?

For Java, the answer is no. The Java Language Specification (JLS 8.4.2) defines a signature as “the method name and argument types.”

That is why overloading methods that differ only in return type is a compile error.

int parse(String input) { ... }

// Compile error: 'parse(String)' is already defined
String parse(String input) { ... }

The reason becomes clear when you look at the call site.

parse("42");  // If the return value is ignored, which parse one should be called cannot be determined

Java always allows calling a method while ignoring its return value. In that case, the compiler has no basis for deciding which of the two parse to call. That is why the return type is excluded from the signature entirely. C++ also forbids overloading based only on the return type for the same reason.

Here is a useful detail. At the JVM bytecode level, the return type is also part of the information used to distinguish methods. In a class file, methods are identified by descriptors such as parse(Ljava/lang/String;)I, whose final I is the return type (int). In other words, “the return type is not part of the signature” is a rule of the Java language, not a rule of the JVM runtime. This gap enables tricks such as generic bridge methods.

Diagram of method-signature components — name, parameter types, count, and order are common to Java and Swift; argument labels and return type are included only in Swift
The same question has different answers in Java and Swift — the two orange cells show the language-specific differences

Swift gives a different answer

Ask the same question about Swift and the answer flips. In Swift, overloading works even when only the return type differs.

func random() -> Int { Int.random(in: 0...100) }
func random() -> Double { Double.random(in: 0...1) }

let n: Int = random()     // Int Call the version
let x: Double = random()  // Double Call the version

The Swift compiler resolves overloading by also examining the type in which the call result is used (the type context). Without context, however, it reports an error.

let value = random()  // Compile error: ambiguous use of 'random()'

Swift signatures have another distinctive element: the argument label is part of the function name.

func move(from start: Point, to end: Point) { ... }
func move(in direction: Direction) { ... }

The formal names of these two functions are not move, but move(from:to:) and move(in:), respectively. Even with identical parameter types, different labels make them different functions. This is exactly the opposite of Java, where parameter names are completely omitted from the signature.

To summarize:

Component Java Swift
Method name Included Included
Parameter types, count, and order Included Included
Parameter name (label) Not included Included (argument label)
Return type Not included Included (resolved by context)

If you can answer “It depends on the language: no for Java, yes for Swift” when asked whether the return type is part of a signature, you understand the concept properly.


Three situations where signatures matter in practice

Knowing only the definition is exam knowledge. Signatures do real work in other situations.

Situation 1 — Distinguishing overloading from overriding

Overloading means “same name, different signature,” while overriding means “reimplementing the same signature as the parent.” Neither concept can be defined without signatures.

If you get a signature subtly wrong while overriding, the compiler interprets it as new overloading instead. The parent method remains intact, while yours is never called—a quiet bug. Java’s @Override and Swift’s override keywords exist to catch exactly this mistake at compile time.

Situation 2 — Adopting interfaces and protocols

Implementing an interface (or protocol) ultimately means providing a method that exactly matches the required signature.

When a Swift delegate method is implemented but never called, a signature mismatch is often the cause. A tiny difference—whether a parameter is optional, or whether the label is for or at—turns it into a separate method that does not satisfy the protocol requirement. Unlike mandatory requirements caught by the compiler, optional requirements are silently ignored, making these bugs harder to find.

Situation 3 — Changing a signature breaks compatibility

When building a library or shared team module, the signature of a public method is a contract with the outside world.

Adding a parameter, changing a type from Int to Int64, or changing even one argument label in Swift breaks all code that called the method. This is a breaking change and a typical reason to increase the major version under semantic versioning.

That is why mature libraries add a method with the new signature and keep the old one deprecated instead of changing the existing signature. They do not unilaterally break the contract represented by it.

Illustration of a code bridge collapsing after editing a public API contract — a breaking change caused by modifying a method signature
The moment you put a pen to a public signature, the bridge used by its callers collapses

What to remember when designing signatures

Once you view a signature as a contract, the design criteria become clear.

Design on the assumption that a public signature is difficult to change. Internal methods can change freely, but public API signatures deserve the most thought before first release. Fixing them later can cost dozens of times more.

If parameters are likely to grow, group them into a type. A signature with four or five parameters invites ordering mistakes, and each addition becomes a breaking change. Grouping them in a configuration object or struct lets you extend it while preserving the signature.

Consecutive parameters of the same type are a warning sign. If the signature alone cannot tell you which of transfer(account1, account2) is the withdrawal account, use labels in Swift, such as transfer(from:to:), or wrap parameters in meaningful types in Java so the signature itself explains how to use the method.


Summary

  • A method signature is the identifying information a compiler uses to distinguish methods; the shared core is the name plus parameter types, count, and order.
  • Whether the return type is included depends on the language. Java and C++ exclude it (overloading by return type alone is not allowed), while Swift includes it (resolved through type context). In Swift, argument labels are also part of the function name.
  • A signature is the criterion for distinguishing overloading from overriding, the matching condition for implementing an interface, and, for a public API, a contract with the outside world.
  • Changing a public signature is a breaking change. Add a new one instead, and invest the most effort in the initial design.

Further reading