The essentials — what you need to know to start reading and writing Go
// Explicit type
var name string = "hello"
// Inferred type
count := 42
// Constants
const Pi = 3.14
// Multiple declaration
var (
x int
y float64
)
char *name = "hello"; int count = 42; #define Pi 3.14 int x; double y;
let name = "hello"; let count = 42; const Pi = 3.14; let x; let y;
:=. No var keyword needed for local variables. Constants are compile-time values, not runtime bindings.
// Function declaration
func add(a int, b int) int {
return a + b
}
// Multiple return values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("div by zero")
}
return a / b, nil
}
// Anonymous function
double := func(x int) int {
return x * 2
}
int add(int a, int b) {
return a + b;
}
// No multiple returns in C
// Use pointers or structs
int divide(double a, double b,
double *result) {
if (b == 0) return -1;
*result = a / b;
return 0;
}
// C99 lambda (limited)
int (*dbl)(int) = [](int x) {
return x * 2;
};
function add(a, b) {
return a + b;
}
// JS can return arrays/objects
function divide(a, b) {
if (b === 0)
throw new Error("div by zero");
return [a / b, null];
}
const double = (x) => x * 2;
error. Callers check if err != nil — Go's equivalent of try/catch.
// Struct definition
type User struct {
Name string
Age int
}
// Method (pointer receiver)
func (u *User) Birthday() {
u.Age++
}
// Interface (implicit)
type Writer interface {
Write([]byte) (int, error)
}
// Any type with Write() satisfies it
// No "implements" keyword needed
// Struct
typedef struct {
char *name;
int age;
} User;
// Method (no receiver, pass explicit)
void user_birthday(User *u) {
u->age++;
}
// Interface via function pointers
typedef struct {
int (*write)(void *self,
const char *buf,
int len);
} Writer;
// Must explicitly register
// Class (ES6)
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
birthday() {
this.age++;
}
}
// Interface via duck typing
// Any object with write() works
// No formal interface needed
class FileWriter {
write(data) { ... }
}
implements keyword. This is Go's most powerful design pattern. It means you can define interfaces after the fact, and existing types automatically satisfy them.
file, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Custom errors
return fmt.Errorf(
"user %d not found: %w", id, err,
)
FILE *f = fopen("data.txt", "r");
if (f == NULL) {
perror("open failed");
exit(1);
}
fclose(f); // must call manually
// Error codes or errno
if (read(fd, buf, sz) == -1) {
perror("read");
}
try {
const data = fs.readFileSync(
"data.txt"
);
} catch (err) {
console.error(err.message);
}
// async/await
try {
const data = await readFile();
} catch (err) {
// handle
}
defer runs when the function exits (like finally). The %w verb wraps errors for errors.Is() and errors.As() checks.
// Only one loop: for
for i := 0; i < 10; i++ { }
// While-style
for x > 0 {
x /= 2
}
// Infinite loop
for { break }
// Range over collection
for i, v := range items { }
// If with init statement
if err := do(); err != nil {
log.Fatal(err)
}
for (int i = 0; i < 10; i++) { }
while (x > 0) {
x /= 2;
}
do { } while (cond);
// switch falls through by default!
switch (x) {
case 1: break;
case 2: break;
}
for (let i = 0; i < 10; i++) { }
while (x > 0) {
x /= 2;
}
for (const [i, v] of items) { }
// switch does NOT fall through
switch (x) {
case 1: break;
case 2: break;
}
Go uses structs + methods + interfaces. No class hierarchy, no extends, no super. Composition over inheritance, enforced by the language.
Lightweight concurrent functions. Launch with go keyword. Managed by Go runtime, not the OS. Thousands can run simultaneously.
Communicate between goroutines with channels. chan is a typed pipe. Send with <-, receive with <-. No callbacks, no event emitters.
defer schedules a call until function exit (like finally). panic is a fatal error. recover catches panics. No try/catch.
Organize code in packages. import paths. export by capitalizing the first letter. main package builds an executable.
nil is Go's null. Slices, maps, channels, pointers, and interfaces can be nil. Always check before use. No undefined/null confusion.
C developers: You'll miss pointer arithmetic and header files. You won't miss manual memory management. Go's garbage collector is fast enough for 99% of use cases.
JavaScript developers: You'll miss async/await and dynamic typing. You won't miss node_modules and build tooling. Go compiles to a single binary with zero dependencies.
The core trade-off: Go gives you less freedom (no generics until 1.18, no inheritance, no try/catch) in exchange for more consistency. Every Go codebase looks the same. That's a feature, not a limitation.