Polymorphism comes in two distinct forms, resolved at different times:
Runtime polymorphism (dynamic / 'true' polymorphism) — the specific method implementation is chosen while the program is running, based on the actual type of the object. This is achieved through method overriding (a subclass provides its own version of a method defined in its parent — see the previous page's Circle/Rectangle example) combined with dynamic dispatch. This is what most people mean by 'polymorphism' in OOP.
Compile-time polymorphism (static polymorphism) — the specific method to call is determined while the code is being compiled, based on the number, types, or order of arguments in the call. This is typically achieved through method overloading — defining multiple methods with the same name but different parameter signatures within the same class:
class Calculator {
function add(int a, int b): int { return a + b }
function add(int a, int b, int c): int { return a + b + c }
function add(float a, float b): float { return a + b }
}
calc.add(2, 3) // calls the two-int version
calc.add(2, 3, 4) // calls the three-int version
calc.add(2.5, 3.5) // calls the float version
The compiler picks which add to call based on the arguments supplied, before the program even runs. (Note: not every language supports true method overloading this way — PHP, Python, and JavaScript don't, and instead use default/variadic parameters or manual type-checking inside a single method to achieve similar flexibility; Java, C++, and C# support it natively.)
A simple way to tell them apart: if the decision of 'which code runs' depends on the object's actual type at runtime, that's runtime polymorphism. If it depends only on the arguments visible at the call site, resolvable before the program runs, that's compile-time polymorphism.