Structs and interfaces: simpler modeling than JavaScript classes
Model data and behavior in Go without carrying over JavaScript class instincts too literally.
by the end of this lesson you can
- →Uses a struct to hold dependencies
- →Defines only the interface behavior the consumer actually needs
- →Avoids a direct one-for-one class translation
Overview
JavaScript gives you objects, prototypes, classes, and structural flexibility. Go gives you structs for data and interfaces for behavior, but it expects you to keep both smaller and more explicit.
In JavaScript/Node, you often
mix data and behavior through classes or object literals and rely on flexible object shape for many abstractions.
In Go, the common pattern is
to use structs for concrete data and narrow interfaces only where behavior contracts are genuinely needed.
why this difference matters
This helps Go code stay flatter and easier to reason about than a direct port of JavaScript class habits.
JavaScript/Node
class User {
constructor(name) {
this.name = name;
}
greet() {
return `hello ${this.name}`;
}
}Go
type User struct {
Name string
}
func (u User) Greet() string {
return "hello " + u.Name
}Deeper comparison
JavaScript/Node version
class Mailer {
send(message) {}
}
class Notifier {
constructor(mailer) {
this.mailer = mailer;
}
notify(message) {
return this.mailer.send(message);
}
}Go version
type Mailer interface {
Send(message string) error
}
type Notifier struct {
mailer Mailer
}
func (n Notifier) Notify(message string) error {
return n.mailer.Send(message)
}Reflect
What gets simpler when you stop carrying JavaScript class habits directly into Go?
what a strong answer notices
A strong answer points to smaller interfaces, flatter data models, and less abstraction built only for flexibility's sake.
Rewrite
Rewrite this class-oriented JavaScript pattern into Go using a struct plus a narrow interface.
Rewrite this JavaScript/Node
class Cache {
get(key) {}
}
class UserService {
constructor(cache) {
this.cache = cache;
}
load(id) {
return this.cache.get(id);
}
}what good looks like
- Uses a struct to hold dependencies
- Defines only the interface behavior the consumer actually needs
- Avoids a direct one-for-one class translation
Practice
Design a Go service that depends on a storage implementation without reproducing a JavaScript-style class hierarchy.
success criteria
- Separates data and behavior clearly
- Uses a narrow interface at the consumer boundary
- Keeps the abstraction focused on the actual use case
Common mistakes
- Porting JavaScript classes one-to-one into Go.
- Creating broad interfaces too early because JavaScript code was structurally flexible.
- Adding methods to structs just because the original object had them.
takeaways
- ●This helps Go code stay flatter and easier to reason about than a direct port of JavaScript class habits.
- ●A strong answer points to smaller interfaces, flatter data models, and less abstraction built only for flexibility's sake.
- ●Separates data and behavior clearly