Building Modular Code with Composition: Go vs TypeScript

This article explores how Go and TypeScript support composition to build modular, reusable code. TypeScript uses type intersections, while Go relies on interface embedding. Both avoid deep inheritance trees, making code more maintainable, flexible, and DRY.


This content originally appeared on HackerNoon and was authored by Gokul

\ If you've worked with class inheritance long enough, you’ve probably reached a point where it starts feeling frustrating and restrictive. Then we all know and can appreciate how Object Composition patterns can simplify code and make express behavior of code better. This is not a write up on Composition vs Inheritance, for more on that take a look at Composition over Inheritance

\

:::tip Instead of thinking in terms of “A is a B,” composition lets us think in terms of “A has behavior X”. This approach makes our code more modular, flexible, and reusable.

:::

\ Go is not an object-oriented language. Staying true to its "Keep it Simple" philosophy, Go does not support classes, objects, or inheritance.

Interface Composition in TypeScript

In Typescript we can leverage type intersections (&) to compose types, building types with all the features we need.

\ Let’s say we’re working with animals. Some animals live on land, some in water, and some (like amphibians) can do both. Instead of using inheritance to model this, we can compose behaviors:

\

type Animal = {
    sound: () => string
}

type AquaticAnimal = Animal & {
    swim: () => void
}

type LandAnimal = Animal & {
    walk: () => void
}

type Amphibian = AquaticAnimal & LandAnimal

const dolphin: AquaticAnimal = {
    sound() {
        return 'ee ee ee'
    },
    swim() {
        console.log('🐬 🐬 🐬')
    },
}

const alligator: Amphibian = {
    sound() {
        return 'sssss'
    },
    swim() {
        console.log('💦 💦 💦')
    },
    walk() {
        console.log('🐊 🐊 🐊')
    },
}

console.log((alligator as Animal).sound()) // Works fine
console.log((dolphin as LandAnimal).walk()) // TypeScript will warn you!

Why This Works Well

  • No rigid hierarchies – Instead of a deep class tree, we mix and match behaviors.
  • Flexibility – We can create new types by combining existing ones, without modifying the originals.
  • Compile-time safety – TypeScript prevents us from calling walk() on an AquaticAnimal.

\ But what about Go? Since Go doesn’t have classes or inheritance, how does it handle composition?

Interface Composition in Go

How would we go about achieving this in Go you ask?

\ In Go, you can embed interfaces inside each other, kind of like snapping Lego blocks together to build something bigger. This makes it easy to break down complex ideas into smaller, more manageable pieces.

\ Since we're composing functionality instead of stacking up rigid inheritance chains, we get way more flexibility and avoid those fragile, complicated class hierarchies. Plus, embedding means less copy-pasting, so we keep our code DRY :open_umbrella: without extra hassle.

\

package main

import "fmt"

// Define Animal behavior
type Animal interface {
    Sound() string
}

// Define Aquatic behavior
type AquaticAnimal interface {
    Animal
    Swim() string
}

// Define Land behavior
type LandAnimal interface {
    Animal
    Walk() string
}

// Amphibian embeds both AquaticAnimal and LandAnimal
type Amphibian interface {
    AquaticAnimal
    LandAnimal
}

// Dolphin implements AquaticAnimal
type Dolphin struct{}

func (d Dolphin) Swim() string {
    return "🐬 🐬 🐬"
}

func (d Dolphin) Sound() string {
    return "ee ee ee"
}

// Crocodile implements Amphibian (both swimming and walking)
type Crocodile struct{}

func (c Crocodile) Swim() string {
    return "💦 💦 💦"
}

func (c Crocodile) Walk() string {
    return "🐊 🐊 🐊"
}

func (c Crocodile) Sound() string {
    return "argh"
}

func main() {
    // Create instances
    willy := Dolphin{}
    ticktock := Crocodile{}

    // Call behaviors
    fmt.Println(willy.Sound()) // ee ee ee
    fmt.Println(willy.Swim())  // 🐬 🐬 🐬

    fmt.Println(ticktock.Sound()) // argh
    fmt.Println(ticktock.Swim())  // 💦 💦 💦
    fmt.Println(ticktock.Walk())  // 🐊 🐊 🐊

    // Using interface values
    var animal Animal = willy
    fmt.Println(animal.Sound()) // ee ee ee

    animal = ticktock
    fmt.Println(animal.Sound()) // argh
}

Real world examples

Go's standard library provides several fundamental interface types that form the backbone of its I/O and string-handling capabilities. Some key examples include:

\

  • fmt.Stringer defines a String() method, allowing a type to represent itself as a string.
  • io.Reader reads data into a byte slice, commonly used for streaming input.
  • io.Writer writes a byte slice to an output destination, such as a file or network connection.

\ These interfaces are widely implemented across the standard library and third-party libraries, enabling seamless integration with files, network I/O, custom serializers, and more.

\ A great example is net.Conn from the net package, which implements both io.Reader and io.Writer:

\

  • As an io.Reader, net.Conn allows you to call Read() to receive incoming data.
  • As an io.Writer, it supports Write(), enabling data transmission over the connection.

\ This dual implementation makes net.Conn a powerful tool for handling network communication efficiently.

Conclusion

Composition helps us avoid deep, rigid hierarchies and instead focus on what objects can do.

\

  • In TypeScript, we achieve composition using type intersections (&), allowing us to mix and match behaviors.
  • In Go, we use interface embedding, making it easy to compose functionality without inheritance.
  • Go’s standard library is built on interface composition, making it highly modular and reusable.

\ By thinking in terms of "has-a" instead of "is-a", we can write cleaner, more maintainable code across different programming languages.

\ Next Steps: Try refactoring some of your own code using composition instead of inheritance. You’ll be surprised at how much cleaner and more flexible it becomes!

Bonus question

Will it compile? If not, why? Drop your answers in the comments! 🤔

\

package main

type A interface {
    sayHi() string
}

type B interface {
    sayBye() string
}

type C interface {
    A
    B
}

func main() {
}

Further Reading


Hope you enjoyed reading this as much as I enjoyed writing it. \n

If this helped you think differently about composition, share it with a fellow developer. Also, if you’ve got thoughts or questions, drop them in the comments—I’d love to discuss more!


This content originally appeared on HackerNoon and was authored by Gokul


Print Share Comment Cite Upload Translate Updates
APA

Gokul | Sciencx (2025-04-02T17:40:29+00:00) Building Modular Code with Composition: Go vs TypeScript. Retrieved from https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/

MLA
" » Building Modular Code with Composition: Go vs TypeScript." Gokul | Sciencx - Wednesday April 2, 2025, https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/
HARVARD
Gokul | Sciencx Wednesday April 2, 2025 » Building Modular Code with Composition: Go vs TypeScript., viewed ,<https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/>
VANCOUVER
Gokul | Sciencx - » Building Modular Code with Composition: Go vs TypeScript. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/
CHICAGO
" » Building Modular Code with Composition: Go vs TypeScript." Gokul | Sciencx - Accessed . https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/
IEEE
" » Building Modular Code with Composition: Go vs TypeScript." Gokul | Sciencx [Online]. Available: https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/. [Accessed: ]
rf:citation
» Building Modular Code with Composition: Go vs TypeScript | Gokul | Sciencx | https://www.scien.cx/2025/04/02/building-modular-code-with-composition-go-vs-typescript/ |

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.