Dylan Falconer

Join 500+ game programmers getting weekly tips.

C for JavaScript Developers

Learning a systems language will:

This article is for those of you interested in taking a crack at C (or any systems language) from the perspective of a web developer.

I share a bit of my story and the insights I have gleaned from taking this path.

Reading time is about 6 minutes.

First, a little story

I was in my mid twenties. I didn’t have a proper Computer Science education. I hadn’t even finished high school.

Spending half a decade in the web industry taught me surprisingly little about programming.

I was a “glue-coder”. I received praise and more money for being a good glue-coder. I wasn’t satisfied.

For you, the paycheque may be enough to stay a glue-coder. For me, it wasn’t about the money.

So, I quit my web dev job and went to get a Diploma of Video Game Programming.

Unfortunately, we only learnt how to use Unreal Engine 3 Visual Scripting.

I went to University to learn Computer Science. In year 1, we only learnt about ethics of IT and some basic Linux stuff I already knew by that point.

I dropped out. I went back into the web industry and continued gluing.

I decided to teach myself C to gain more understanding of what was going on. That was five years ago.

Now I can see how little I knew and how little I needed to know. I could build front-end web applications that were “good enough”.

If any of that resonates with you, I wrote this for you.

I hope this helps get you up that first hill.

Manual memory management

Have you ever tried to write some high-performance code in JavaScript?

A little game or animation using Canvas?

You may have noticed that performance sucks.

If you go deep enough, you may find yourself implementing an Object Pool.

An Object Pool is a strategy to prevent the Garbage Collector from deleting your objects.

This way, there won’t be those sawtooth spike patterns in your memory usage that cause lag.

Let’s say you are making a simple video game with a space ship flying through space.

Enemies appear from the top of the screen. You have a ship flying “up”. I’m sure you’ve seen games like this.

When ships and missiles explode, they create particles. These particles vary in colour, size, lifetime, and velocity.

You find that when many enemies explode all at once the game lags.

You profile the game. The particles spawning and the Garbage Collector cleaning them up is causing lag.

“What if they didn’t get cleaned up and instead we reuse them?”

The answer to that question is an Object Pool.

One field of a particle could be isActive or you could keep a separate free-list.

Regardless of the details, you have now implemented a memory allocator.

Perhaps memory management is not as scary as many would have you believe.

Pointers

You already use them in JavaScript. Sort of.

When you pass an Object or an Array to a function in JavaScript, you don’t get a copy, you get a reference.

There is a difference between references and pointers.

In JavaScript, you can’t pass an array to a function then read outside the memory of that array.

You can extend the array, even using negative indices, but let’s not go there.

C doesn’t have such bounds checking. That means by writing off the end of an array, you corrupt other memory. You can even corrupt the call-stack, causing all kinds of unpredictable behaviour.

In practice, there are tools to prevent these bugs, but it’s important to know.

In either case, mutating the reference will mutate the value passed in at the call-site.

Types and Structs

I suspect this is less of a problem nowadays. A lot of JavaScript developers use TypeScript.

Regardless, I’ll cover what I would have liked to have known about C’s types when getting into it.

Types are a way to tell the compiler the size of the data you are using. This doesn’t come up in JavaScript, but it’s very important in C and how to think about memory.

When you define a struct in C, it’s easy to start thinking in weird abstract ways again.

Think about it like this:

A struct is a way to bundle values together and refer to them as one “thing”.

They are not objects, though you may hear C programmers refer to them as objects. It’s not the same - there are no methods and modification is not possible.

I’ll refer to a struct value as an “instance”.

An instance of a struct is a block of memory with enough bytes to store the fields in order. They may sometimes have padding between fields or at the end, but I’ll skip over that.

In C, the size of types vary depending on platform. For most things we use 64-bit architecture and have been for over a decade.

That means int and float are 4 bytes and char is a single byte.

As I said earlier, types are kind of telling the compiler the size of things.

If you cast an int pointer to a char pointer then read the value, you will get only a single byte.

If the number happens to be less than 128, you’ll see no problem. If it’s higher, you may get a negative number instead as a single char represents -127 to 127.

A working example

Here’s a working program that shows what I’m talking about, with extensive comments.

It includes a new concept called “casting”. This allows you to refer to one pointer type as another type. It’s very important to know this.

Why is it useful to think of them as block of memory with byte offsets?

It keeps you thinking of the program in a practical way.

For example, let’s say you have a library that needs the float values but nothing else.

The library can’t understand your type, but it can understand byte offsets.

Libraries will often have built in the concept of “stride”. That is, how many bytes are between each value it wants to operate on.

If you pass in an array of float to the function, you can use a stride of 0.

Pass in an array of struct instances and then you’ll need to tell the library the size of your type.

The lack of runtime type information is why thinking in sizes and offsets is important.

Arrays are fixed size

In C, arrays are always a fixed length.

This may sound limiting, but in practice it’s usual to know the required capacity of your data.

I would recommend, if you need dynamic arrays, to use one of the thousands of libraries that exist. Or, learn how to create your own.

Iterators

There are no iterators or syntactic sugar for looping over stuff.

Since there are no methods, there’s nothing like my_array.forEach.

You will have to get used to writing plain loops.

Strings

Strings are Arrays of char in C with a null-value to denote the end \0.

Personally, I don’t like the way they work by default, so I use my own implementation.

I wrote it to be compatible with normal null-terminated strings for library interfaces.

As with Dynamic Arrays, I recommend using a library or learning how to create your own.

Of course, you’ll have to learn how to do Unicode to have proper language support. It is quite fun from what I’ve read - I haven’t added it myself.

The biggest change is mindset

We can go over the language differences all day, but it won’t matter much if you keep the JavaScript mindset.

I’m speaking from experience here. My mindset had to change to get better at C and programming in general.

I’ve never spoken to anyone about this before, but I used to think of programs in very fuzzy ways.

In my mind’s eye I’d visualise graphs of objects all talking to each other.

I’d obsess about code cleanliness.

I’d comment every line with useless comments.

I went down the OOP SOLID rabbit hole and tried very hard to follow those rules.

None of that stuff made programming easier.

What actually helped was a talk by Mike Acton that I’m sure many people in this sphere are familiar with.

“Programs are for data transformation”

You have X. You want Y. The program (or subset) is how you convert X to Y.

All that’s in a program: plain old data and instructions that operate on that data.

Adding layers of complexity should not be the norm. Yet it is - especially in JavaScript land.

You are better off only adding complexity after careful consideration of the trade-offs.

How this mindset change helps

These are examples of web based work I’ve done recently that benefited from a new mindset.

A recent SaaS project

Over the past little while I’ve been working on a web app.

In the past, I would have looked up what the hot JavaScript framework of the month is and used that.

I would have found some bleeding edge stuff to add to it that breaks the bundler… and spent days fixing build issues.

What did I do differently? I thought about what I actually need to get done and what the most practical approach was.

I made a back-end in Golang. I made a front-end in HTML, CSS, JS with htmx.

I added a websocket connection to send htmx events from the server to the client for dynamic updates.

There’s more stuff around auth and whatever else that you’d have to do regardless, but the point is:

I was able to focus on the data transformations required to give the user the experience I wanted.

I chose the stack based on efficiency and requirements, not because it’s flavour of the month.

I didn’t have to think about React hooks or component lifetimes.

I didn’t have to mess with webpack or vite - my build system is a few lines in a bash file.

A recent web client

I had a website client a few months ago who wanted a very simple site.

She needed a home page with some static information, a photo, and a booking form.

She wanted to be able to update some of the booking form content:

And she refused to pay a monthly fee to somewhere like Jotform.

How would you have created this? Wordpress? A service like Wix or Squarespace?

Once again, I used Go and htmx with HTML, CSS, JS.

It took only a few lines of code to add this functionality.

The site is fast, has good SEO, and it’s easy for the client to update it.

Conclusion

Learning how to think about programming in a different manner has been invaluable.

I can now consider and create programs that would have become tangled spaghetti code if my past self tried.

I’m sure a lot of that I owe to general experience.

OOP did not help with this problem.

Notes

Thanks for reading!

If you found this useful, join the mailing list for more!


← Articles

Join 500+ game programmers getting weekly tips.