How do interfaces differ between Java, C#, Go and Python, and what does that mean for the way you design?
The big difference is nominal versus structural typing. Java and C# are nominally typed for interfaces: a class must say it implements an interface, and the compiler checks the methods. Go is structural for interfaces: a type satisfies an interface automatically if it has the right methods, with no declaration at all. Python is dynamic, so an object satisfies any interface it happens to have the methods for, checked at run time.
public class Main {
public static void main(String[] args) {
Notifier n = new ConsoleNotifier();
System.out.println(n.notify("hello"));
ConsoleLogger l = new ConsoleLogger();
l.log("hello");
System.out.println("duck typing works at run time");
}
}
interface Notifier {
String notify(String message);
}
class ConsoleNotifier implements Notifier {
public String notify(String message) { return "notified: " + message; }
}
class ConsoleLogger {
void log(String message) { System.out.println("logged: " + message); }
}The example shows the Java behaviour and the alternative side by side. The notifier is explicitly declared as implementing the interface, so the compiler guarantees the contract. The console logger never mentions any interface, yet it has the same shape as one that does, and its method can be called directly. The last line makes the point that duck typing works at run time in languages that allow it.
The design consequences differ. In Java, the interface is a real type you can name in a signature, and the compiler is what enforces the relationship. In Go, the interface is usually defined by the consumer, which is why Go code tends to have small interfaces owned by the package that uses them. In Python, the relationship is invisible to the tooling, so explicit base classes or protocols are often used to bring the checking back.
The interview point is that the language choice shapes where you put the contract. Nominal languages let you define an abstraction once and share it; structural languages let each consumer define the tiny interface it needs, and the contract stays local to the code that uses it.