From BASIC to JavaScript: The Irony of Modern Spaghetti Code

Or how we learned to love GOTO again, but with fancier names

Editorial 493 views 5 minutes
foto de Del BASIC al JavaScript: La Ironía del Código Espagueti Moderno

A Reflection From the Commodore 64

It was the year... well, that doesn't matter. What matters is that many of us learned to program in an era when things were brutally simple. A Commodore 64, 64KB of RAM, and BASIC as the language. No frameworks, no transpilers, no npm install. Just you, the manual, and numbered lines.

And it turns out that after decades of "evolution" in software development, we've reached an uncomfortable conclusion: modern code is basically the same old GOTO, but with fancier names.

The Original Sin: The Dreaded GOTO

For years, GOTO has been the villain of structured programming. "Spaghetti code!" shouted the purists. "Incomprehensible control flow!" they declared in their academic papers.

And they were right. This was a nightmare:

10 PRINT "Program start"
20 GOTO 100
30 PRINT "This never executes"
40 GOTO 200
50 PRINT "Neither does this"

100 PRINT "Doing something important"
110 IF X = 1 THEN GOTO 300
120 GOTO 400

200 PRINT "How did I get here?"
210 GOTO 50

300 PRINT "Branch A"
320 GOTO 500

400 PRINT "Branch B" 
410 GOTO 600

500 PRINT "Continuing A"
510 GOTO 40

600 PRINT "End... or not?"

Impossible to follow. A maze of jumps with no apparent logic.

The "Modern" Solution: Functions with Pretty Names

Then came functions, structured programming, and later object-oriented programming. "No more GOTO!" they celebrated. "Clean and maintainable code!" they promised.

Let's see what "modern" code looks like:

async function processOrder() {
  const user = await getUser()
  const products = await validateProducts(user)
  const payment = await processPayment(products)
  const shipping = await handleShipping(payment)

  return await confirmOrder(shipping)
}

async function getUser() {
  return await searchDatabase('users')
}

async function validateProducts(user) {
  const products = await getProducts()
  return await checkAvailability(products, user)
}

async function processPayment(products) {
  const method = await selectPaymentMethod()
  return await chargeCard(method, products)
}

// ... and so on

Wait a minute. Let's analyze this:

  1. processOrder() says: "Go to getUser()"
  2. getUser() says: "Go to searchDatabase()"
  3. Back to processOrder(), which says: "Now go to validateProducts()"
  4. validateProducts() says: "Go to getProducts()"
  5. Then says: "Go to checkAvailability()"
  6. Back again, and processOrder() says: "Now go to processPayment()"

Sound familiar? It's exactly the same as:

10 GOSUB 1000  ' Go get user
20 GOSUB 2000  ' Go validate products  
30 GOSUB 3000  ' Go process payment
40 GOSUB 4000  ' Go handle shipping
50 GOSUB 5000  ' Go confirm order
60 END

1000 REM Get user
1010 GOSUB 6000  ' Go search database
1020 RETURN

2000 REM Validate products
2010 GOSUB 7000  ' Go get products
2020 GOSUB 8000  ' Go check availability
2030 RETURN

The Brutal Reality: Only the Names Changed

The difference between GOTO 1000 and await getUser() is purely cosmetic:

| BASIC               | "Modern" JavaScript  |
| ------------------- | -------------------- |
| `GOSUB 1000`        | `await myFunction()` |
| `RETURN`            | `return value`       |
| Line numbers        | Function names       |
| One sequential file | 47 scattered files   |

The control logic is identical: "Stop what you're doing, go execute this other thing, and come back with the result".

The New Hell: Distributed Spaghetti Code

But wait, because it gets worse. At least in BASIC everything was in one file, in sequential order. Modern JavaScript is a new kind of torture:

File: main.js

import { initializeApp } from './utils/init.js'
import { processOrder } from './services/order.js'

async function init() {
  await initializeApp()
  await processOrder()
}

init()

File: services/order.js

import { User } from '../models/User.js'
import { PaymentService } from '../external/payments.js'
import { validateProducts } from '../validators/products.js'

export async function processOrder() {
  const user = await User.get()
  const products = await validateProducts(user)

  return await PaymentService.process(products)
}

File: validators/products.js

import { StockService } from '../services/stock.js'
import { ProductRepository } from '../repositories/products.js'

export async function validateProducts(user) {
  const products = await ProductRepository.getByUser(user.id)
  return await StockService.verify(products)
}

It's a treasure hunt! To understand what the program does you need to:

  1. ✅ Find the entry file (which one is it?)
  2. ✅ Follow imports like they were GOTOs
  3. ✅ Jump between files like a pinball
  4. ✅ Keep the state of 15 functions in your head simultaneously
  5. ✅ Pray there are no circular imports

The Nostalgia of Simplicity

Let's remember what programming on the Commodore 64 was like:

10 REM *** INVENTORY SYSTEM ***
20 REM *** BY: YOUR NAME ***
30 REM *** DATE: 1985 ***
40 CLS
50 PRINT "=== STORE INVENTORY ==="
60 PRINT
70 GOSUB 1000  ' SHOW MENU
80 GOSUB 2000  ' PROCESS OPTION
90 IF OPTION <> 9 THEN GOTO 70
100 PRINT "GOODBYE!"
110 END

1000 REM *** SHOW MENU ***
1010 PRINT "1. ADD PRODUCT"
1020 PRINT "2. SEARCH PRODUCT"  
1030 PRINT "3. LIST PRODUCTS"
1040 PRINT "9. EXIT"
1050 PRINT "OPTION: ";
1060 INPUT OPTION
1070 RETURN

2000 REM *** PROCESS OPTION ***
2010 IF OPTION = 1 THEN GOSUB 3000
2020 IF OPTION = 2 THEN GOSUB 4000
2030 IF OPTION = 3 THEN GOSUB 5000
2040 RETURN

3000 REM *** ADD PRODUCT ***
3010 PRINT "PRODUCT NAME: ";
3020 INPUT NAME$
3030 PRINT "PRICE: ";
3040 INPUT PRICE
3050 PRINT "PRODUCT ADDED!"
3060 RETURN

What could you do?

  • LIST - See all code at a glance
  • RUN - Execute immediately
  • GOTO 1000 - Jump to any line for debugging
  • ✅ Everything in one file, logical order
  • ✅ Global variables (no shame)
  • ✅ Obvious control flow

The Modern Equivalent: An Ordeal

The same program today would require:

project/
├── package.json
├── webpack.config.js  
├── babel.config.js
├── .eslintrc.json
├── src/
│   ├── index.js
│   ├── components/
│   │   ├── Menu.js
│   │   ├── ProductForm.js
│   │   └── ProductList.js
│   ├── services/
│   │   ├── ProductService.js
│   │   └── StorageService.js
│   ├── utils/
│   │   ├── validators.js
│   │   └── formatters.js
│   └── styles/
│       ├── main.css
│       └── components.css
├── tests/
│   ├── unit/
│   └── integration/
└── node_modules/ (3,000 folders)

To run:

npm install
npm run build
npm run dev

To understand the flow:

  1. 🔍 Find the entry point in package.json
  2. 🔍 Follow imports like breadcrumbs
  3. 🔍 Understand webpack, babel, and the build process
  4. 🔍 Debug with sourcemaps
  5. 🤯 Give up and look for documentation

The Supreme Irony

Modern programmers criticize GOTO, but do this daily:

// "Clean Code" modern style
class UserService {
  async getUser(id) {
    return await this.repository.findById(id)
  }
}

class UserRepository {
  async findById(id) {
    return await this.database.query(`SELECT * FROM users WHERE id = ?`, [id])
  }
}

class Database {
  async query(sql, params) {
    return await this.connection.execute(sql, params)
  }
}

// To get a user:
const user = await userService.getUser(123)

Translated to honest BASIC:

10 GOSUB 1000  ' UserService.getUser()
20 END

1000 GOSUB 2000  ' UserRepository.findById()
1010 RETURN

2000 GOSUB 3000  ' Database.query()
2010 RETURN  

3000 REM Here it actually does something useful
3010 PRINT "SELECT * FROM users WHERE id = 123"
3020 RETURN

It's the same pattern! Except now you need to:

  • ✅ Understand dependency injection
  • ✅ Configure an ORM
  • ✅ Install 47 libraries
  • ✅ 3 classes to do a SELECT

The Moral

I'm not advocating for a return to uncontrolled GOTO. Structured programming did bring real benefits:

  • ✅ Code reusability
  • ✅ Modularity
  • ✅ Testability
  • ✅ Maintainability (when done well)

But we must be honest: we've replaced the brutal simplicity of BASIC with complexity that's often unnecessary.

Lessons from the Commodore 64

Those of us who learned programming in that era developed something valuable: the ability to see control flow directly.

In BASIC:

  • Every line had a number - you knew exactly where you were
  • LIST 100-200 showed you the code you needed
  • GOTO 150 took you exactly where you wanted to go
  • There were no abstraction layers hiding the logic

In modern JavaScript:

  • Functions are scattered across multiple files
  • Imports create dependencies you must track mentally
  • async/await creates the same "jump there" as GOTO
  • Abstractions sometimes hide more than they help

GOTO Never Left

GOTO didn't disappear - it just became more sophisticated and hid behind elegant names.

await getDataFromServerAndProcessWithComplexValidation() is basically GOSUB 2000 with a longer name.

The difference is that now:

  • ✅ Names are descriptive (better for maintenance)
  • ✅ There's scope and encapsulation (better for organization)
  • ✅ There are better tools (debugging, testing, etc.)

But also:

  • ❌ Multiple files create unnecessary complexity
  • ❌ Over-engineering is the norm
  • ❌ Simple problems get complex solutions
  • ❌ The learning curve is astronomical

Final Reflection

Maybe the real lesson isn't that GOTO was bad, but that any paradigm taken to the extreme becomes problematic.

The BASIC of the Commodore 64 taught us something we've forgotten: sometimes brutal simplicity is better than elegant complexity.

Not everything needs to be a microservice. Not everything needs 15 layers of abstraction. Not everything needs a framework.

Sometimes, you just need a simple:

10 PRINT "HELLO, WORLD!"
20 END

And that's perfectly fine.

Contáctanos por WhatsApp

¡Comunícate con nosotros para trabajar con tu nueva gran idea! Responderemos a la brevedad.

Enviar mensaje
Horario de atención: 9am - 6pm
ES EN

Copyright © 2025 Código Móvil!!. All Rights Reserved.