Start learning free

LESSON 187 OF 348 · OBJECT-ORIENTED PROGRAMMING

Method Overriding in Kotlin: open and override

Lesson Read the full explanation

TL ; DR:

When a child class defines a method with the same name as the parent's, the child's version overrides the parent's. Different objects can give the same method name different behavior.


open class Dog {
    open fun bark() { println("Woof!") }
}
class Puppy : Dog() {
    override fun bark() { println("Yip!") }
}


Full lesson:

You've seen that a child class inherits the parent's methods. Overriding is what happens when the child wants to change one of those inherited methods.

Why override?

Subclasses are usually special cases of the parent. A Puppy is a Dog, but it sounds different — "Yip!" instead of "Woof!". Instead of inventing a new method name, you redefine bark() on the Puppy. Callers don't need to know which subclass they're holding — they just call bark() and the right version runs.

How it works in each language

  • C# — the parent method must be marked virtual, the child marks its replacement override. The compiler enforces this pairing.
  • Java — every non-final method is virtual by default; the @Override annotation is optional but tells the compiler to verify the signature matches.
  • Python — defining a method with the same name in the child class replaces the parent's. No keyword required.
  • JavaScript — same as Python: define the method again on the child class and it wins.
  • C++ — the parent method must be virtual; override on the child is optional but compiler-checked.

Your task

The Dog class already defines bark. Inside the Puppy subclass, override bark so it prints "Yip! I am " followed by the puppy's name. The harness creates a Puppy named "Buddy" and calls bark() on it.

Expected output:

Yip! I am Buddy
STEP 1 OF 2
    Ctrl + Enter to check

    KOTLIN · OBJECT-ORIENTED PROGRAMMING

    Method Overriding in Kotlin, explained

    Changing an inherited function

    A subclass inherits its parent's functions. Overriding is what you do when the child wants to change one of them. A puppy is a dog, but it sounds different: Yip! instead of Woof!. Rather than invent a new function name, you redefine bark() in Puppy. Code that holds a dog calls bark() without caring which kind it is, and the right version runs.

    Two keywords, both required

    Kotlin makes overriding explicit on both sides:

    • The parent marks the function open. Functions are final by default, like classes.
    • The child marks its replacement override.
    open class Dog(val name: String) {
        open fun bark() {
            println("Woof! I am $name")
        }
    }
    
    class Puppy(name: String) : Dog(name) {
        override fun bark() {
            println("Yip! I am $name")
        }
    }
    

    The signature of the override must match the parent's: the same name and parameter types. There is no annotation to add. override is a keyword, and the compiler enforces the pairing.

    Both sides of the contract are checked. If you leave out open, the override fails with 'bark' in 'Dog' is final and cannot be overridden. If you leave out override and just write fun bark() in the child, the error says that bark hides a member of the supertype and needs the override modifier. If the signature differs, as in override fun bark(times: Int), the compiler reports that it overrides nothing. These errors are the point: you cannot override by accident or by a typo.

    Which version runs

    The version is chosen by the actual class of the object, not by the type of the variable:

    val buddy = Puppy("Buddy")
    buddy.bark()
    
    val d: Dog = buddy
    d.bark()
    

    Both calls print Yip! I am Buddy. Even though d is declared as a Dog, the object is a Puppy, so its own bark runs. A plain Dog("Rex").bark() still prints Woof! I am Rex.

    Extending instead of replacing

    An override can also build on the parent's version. super refers to the parent:

    class Loud(name: String) : Dog(name) {
        override fun bark() {
            super.bark()
            println("(louder)")
        }
    }
    

    Loud("Max").bark() prints Woof! I am Max and then (louder).

    Overriding versus overloading

    These are different things. Overriding replaces a parent's function with the same name and parameters. Overloading adds another function with the same name but different parameters. If Dog also had fun bark(times: Int), a call such as bark(2) would pick that one by its argument, and Puppy's overridden bark() would be untouched.

    Overrides stay open

    A function marked override is itself open, so a grandchild can override it again, as long as its class is open. To stop that, write final override fun bark(). Overriding it further then fails with the same is final and cannot be overridden error.

    Overriding properties

    Properties follow the same rules. A property can be open in the parent and override in the child, and functions that read it see the child's value:

    open class Dog(val name: String) {
        open val sound: String = "Woof"
        open fun bark() {
            println("$name says $sound")
        }
    }
    
    class Puppy(name: String) : Dog(name) {
        override val sound: String = "Yip"
    }
    

    Puppy("Buddy").bark() prints Buddy says Yip, although bark itself is written only in Dog. Here the child changes data, not behavior, and the parent's function picks it up.

    The exercise

    The starter code gives Dog with an open fun bark() and an empty Puppy. The first step declares override fun bark() inside Puppy, and the second makes it print "Yip! I am " followed by name. The property name is inherited from Dog, so a string template can use it directly:

    open class Dog(val name: String) {
        open fun bark() {
            println("Woof! I am $name")
        }
    }
    
    class Puppy(name: String) : Dog(name) {
        override fun bark() {
            println("Yip! I am $name")
        }
    }
    
    class Solution {
        fun mainFunction() {
            val buddy = Puppy("Buddy")
            buddy.bark()
        }
    }
    

    The output is Yip! I am Buddy.

    Common mistakes

    • Forgetting open. The parent's function must allow overriding.
    • Forgetting override. A same-named function in the child is an error, not a quiet replacement.
    • Changing the parameters. A function with different parameters is a different function, so it cannot carry override. Defining it without the keyword is overloading, which is allowed.
    • Misspelling the text. The output must be exactly Yip! I am Buddy, with the capital Y, the exclamation mark and the name after the space.
    • Calling super.bark() by habit. It would print the dog line first and break this lesson's expected output.

    Key takeaways

    • The parent writes open fun; the child writes override fun with the same signature.
    • The object's actual class decides which version runs.
    • super.name() calls the parent's version from inside an override.
    • final override closes a function to further overriding.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free