Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance

I’ve already covered some of the basics of Go earlier. However, before diving into topics like goroutines and wait times, I think it’s important to first discuss a few key aspects of **Go **and how it’s typically used.

Closures

As we alread…


This content originally appeared on DEV Community and was authored by Saksham Malhotra

I’ve already covered some of the basics of Go earlier. However, before diving into topics like goroutines and wait times, I think it’s important to first discuss a few key aspects of **Go **and how it’s typically used.

Closures

As we already know, Go has closures just like javascript. The only differences are:

  • Strong typing make closures more predictable.
  • Go closures capture variables by reference, not by value (similar to using var in JS instead of let or const).
  funcs := []func(){}

    for i := 0; i < 3; i++ {
        funcs = append(funcs, func() {
            fmt.Println(i)
        })
    }

    for _, f := range funcs {
        f()
    }

Stringer

Go has a built-in interface called fmt.Stringer. When you print a value using fmt.Println, fmt.Printf, or similar functions, Go’s formatting code internally checks:

“Does this value implement the Stringer interface?”

If yes then, it calls your custom String() method to get the string representation. Else it prints the default (struct fields, numeric value, etc.).

func (p ProductID) String() string {
    return fmt.Sprintf("Product-%d", p)
}

func main() {
    id := ProductID(42)
    fmt.Println(id) // prints: Product-42
}

Bonus: Go’s Type System Rule - Named types are distinct from their underlying types.

No method overloading

Yes, you heard it right, no more confusion arising from same-name methods. Go’s interface satisfaction is structural and implicit.

  • It doesn’t care which interface you meant to implement.
  • It only cares that your type’s method set matches the interface’s method signatures.

So if two interfaces have identical methods, implementing one automatically implements the other.

type A interface {
    String() string
}

type B interface {
    String() string
}

func (a int) String() string{
   return fmt.Sprintf("%d", a)
}

var a A = 7 
a.String()

The above code won’t compile because func (a int) is invalid in Go — you cannot attach a method to an unnamed type.

Corrected version:

type MyInt int

type A interface {
    String() string
}

type B interface {
    String() string
}

func (a MyInt) String() string{
   return fmt.Sprintf("%d", a)
}

var a A = MyInt(7) 
a.String(); 

Interface values are two-part boxes

A Go interface value is actually a pair of things:
(dynamic type, dynamic value)
Eg:
var a A = MyInt(7)
(dynamic type = MyInt, dynamic value = 7)

Unlike other languages, you’re not “creating an object of the interface”

  • Instead, you’re creating a variable of interface type that stores a value (of any type) that satisfies the interface.
  • The interface variable is like a box that can hold any value, as long as the value provides the required methods.

OR

Interface = Plug socket.
Any type that fits (has the right methods) can plug in; no prior agreement required.

Methods Can Belong to Any Named Type

In Go, you can attach a method to any named type, not just structs or interfaces.

type ProductID int

func (p ProductID) String() string {
    return fmt.Sprintf("Product-%d", p)
}

Now, this brings us to a question:

Why do interfaces exist even though any named type can have methods?

And the answer is simple, the methods attached to a type is encapsulation, but not polymorphism.

What methods to a type can't do:

type ProductID int
func (p ProductID) String() string { return fmt.Sprintf("Product-%d", p) }

type OrderID int
func (o OrderID) String() string { return fmt.Sprintf("Order-%d", o) }

type CustomerID int
func (c CustomerID) String() string { return fmt.Sprintf("Customer-%d", c) }

Without any interface, you would need to write one function per type :

func PrintProduct(p ProductID) { fmt.Println(p.String()) }
func PrintOrder(o OrderID) { fmt.Println(o.String()) }
func PrintCustomer(c CustomerID) { fmt.Println(c.String()) }

Interfaces solve this problem:
They say, I don't care about the type as long as it knows how to do this behavior:

type Stringer interface {
    String() string
}

func PrintAnything(s Stringer) {
    fmt.Println(s.String())
}
PrintAnything(ProductID(1))
PrintAnything(OrderID(2))
PrintAnything(CustomerID(3))

Contract not Inheritance

The interface just describes behavior i.e. a contract, not an implementation.

type A interface {
    Foo()
}

type MyType struct{}

func (m MyType) Foo() {
    fmt.Println("Hello from Foo")
}

var a A = MyType{} 
a.Foo() 

Above thing works because MyType has a method Foo() with the same signature as required by A. It's not inheritance or anything else, but a contract.
So, if A was :

type A interface{
   Foo()
   Foo2()
}

and rest of the code remain same, then it will be a compiler error, since the interface contract says a to have two methods but a = MyType{} is only assigning it one.

Conclusion

Go’s design favors clarity, not magic. It doesn’t hide behavior behind inheritance or overloading; instead, it gives you small, powerful primitives: named types, methods, and interfaces.

Interfaces in Go aren’t about hierarchy but about capability. If a type can do what’s expected, it fits. That’s why Go’s polymorphism feels lightweight yet expressive.

From closures that behave predictably, to interfaces that define contracts rather than family trees, it reminds us that simplicity, when designed well, is not a limitation but a superpower.


This content originally appeared on DEV Community and was authored by Saksham Malhotra


Print Share Comment Cite Upload Translate Updates
APA

Saksham Malhotra | Sciencx (2025-10-24T07:38:02+00:00) Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance. Retrieved from https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/

MLA
" » Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance." Saksham Malhotra | Sciencx - Friday October 24, 2025, https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/
HARVARD
Saksham Malhotra | Sciencx Friday October 24, 2025 » Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance., viewed ,<https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/>
VANCOUVER
Saksham Malhotra | Sciencx - » Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/
CHICAGO
" » Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance." Saksham Malhotra | Sciencx - Accessed . https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/
IEEE
" » Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance." Saksham Malhotra | Sciencx [Online]. Available: https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/. [Accessed: ]
rf:citation
» Go Beyond Basics: Closures, Interfaces, and Why Go Has No Inheritance | Saksham Malhotra | Sciencx | https://www.scien.cx/2025/10/24/go-beyond-basics-closures-interfaces-and-why-go-has-no-inheritance/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.