Go Syntax for C & JavaScript Developers

The essentials — what you need to know to start reading and writing Go

▶ Watch: Go in 100 seconds

Variables & Types

Go
// Explicit type
var name string = "hello"

// Inferred type
count := 42

// Constants
const Pi = 3.14

// Multiple declaration
var (
    x int
    y float64
)
Short declaration := is preferred
C
char *name = "hello";
int count = 42;
#define Pi 3.14

int x;
double y;
No const keyword for true constants
JavaScript
let name = "hello";
let count = 42;
const Pi = 3.14;

let x;
let y;
let/const, no type annotations
Key difference: Go is statically typed but the compiler infers types from :=. No var keyword needed for local variables. Constants are compile-time values, not runtime bindings.

Functions

Go
// 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
}
No semicolons, no parens around params
C
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;
};
C uses pointers for multiple outputs
JavaScript
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;
JS returns anything, no types
Multiple return values are Go's answer to output parameters. The last return is conventionally an error. Callers check if err != nil — Go's equivalent of try/catch.

Structs & Interfaces

Go
// 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
Interfaces are satisfied implicitly
C
// 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
C has no native interface concept
JavaScript
// 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) { ... }
}
JS uses duck typing, no formal interfaces
Go interfaces are implicit. If a type has the right methods, it satisfies the interface — no 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.

Error Handling

Go
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,
)
if err != nil — every time
C
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");
}
C returns NULL or -1, checks errno
JavaScript
try {
    const data = fs.readFileSync(
        "data.txt"
    );
} catch (err) {
    console.error(err.message);
}

// async/await
try {
    const data = await readFile();
} catch (err) {
    // handle
}
JS uses try/catch, no return errors
No try/catch in Go. Errors are values returned as the last return value. defer runs when the function exits (like finally). The %w verb wraps errors for errors.Is() and errors.As() checks.

Loops & Control Flow

Go
// 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)
}
No while, no do-while, no switch fallthrough
C
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;
}
C has for, while, do-while, switch
JavaScript
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;
}
JS has for, for-of, while, switch

Key Concepts to Remember

No Classes, No Inheritance

Go uses structs + methods + interfaces. No class hierarchy, no extends, no super. Composition over inheritance, enforced by the language.

JS: class Foo extends Bar → Go: type Foo struct embeds Bar

Goroutines, Not Threads

Lightweight concurrent functions. Launch with go keyword. Managed by Go runtime, not the OS. Thousands can run simultaneously.

JS: async/await + Promise → Go: go func() + channel

Channels, Not Callbacks

Communicate between goroutines with channels. chan is a typed pipe. Send with <-, receive with <-. No callbacks, no event emitters.

JS: EventEmitter.on('data', cb) → Go: data := <-ch

Defer, Panic, Recover

defer schedules a call until function exit (like finally). panic is a fatal error. recover catches panics. No try/catch.

JS: try/catch/finally → Go: defer/panic/recover

Package System

Organize code in packages. import paths. export by capitalizing the first letter. main package builds an executable.

JS: export default Foo → Go: func Foo() (exported)

No Null, Use Nil

nil is Go's null. Slices, maps, channels, pointers, and interfaces can be nil. Always check before use. No undefined/null confusion.

JS: null/undefined → Go: nil (one value, one meaning)

The Mental Model Shift

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.