Assembly becomes really attractive really quickly.
A floating point
A$ string
A% int
as you would expect ints are quicker.
There were BASIC compilers for C64 which generated significantly faster code just because they actually used ints for integer operations.
What sidestepped part of that conversion was using variables instead of literal constants. So, doing something like N0=0, and using the variable N0 instead of 0 was faster.
Additionally, C64 looked up variables in the order they were defined, so variables defined later were slower. If your program used 0 a lot, you'd want to have N0=0 as the first statement in your program. I typically had N0=0 and N1=1 as those were common to use.
QBasic was so much faster and implemented differently. DEFINT A-Z at the top of the program typically sped it up significantly if you didn't use floating point. This might have worked on GW-BASIC as well.
Which is surprising because one of the advantages of the C64, IIRC, for games was that it had built in sprite manipulation so you didn't have to do the work yourself on the CPU (unless you wanted something more flexible than the one standard sprite format).
It had a nice set of built-in graphics functions and an inline assembler, amongst other things.
I can still remember that VDU 23 was the command to redefine a character, so much easier than doing the same sort of thing on the C64 (copy the character set from ROM to RAM, get the VIC to use the RAM copy with POKE statements, modify the desired character)
IIRC those were actually in the “OS” ROM, with BASIC just having syntactic sugar to call them, rather than being part of the BASIC ROM itself, so you could call them from elsewhere (i.e. your own assembly code, even if you'd paged the BASIC ROM out to make some RAM available in that bit of the address space on the BBC Master).
> and an inline assembler, amongst other things.
That assembler was great when I was cutting my programming teeth and progressing a bit beyond BASIC to better understand the hardware.
BBC BASIC has some other crucial, more basic, features that were missing from other 8-bit micros. Actual named functions and procedures were key¹, and long variable names were useful² where some BASICs only allowed one or two characters³.
--------
[1] Other BASICs had no more than GOTO and GOSUB/RETURN, usually only allowing you to jump to line numbers, not names/labels. There was even some support for local variables making proper recursion possible up to a point (limited by the small stack).
[2] if limiting for long code due to memory constraints
[3] or allowed more but only respected the first couple, BBC BASIC syntax in theory allowed up to 248 characters⁴ but only respected the first 40 and practically you still limited yourself to a few (say, up to 10 for some for clarity, 1 for things like loop counters to save space in your code, CPU time in the interpreter, and space in the interpreter's variable dictionary).
[4] the absolute limit to what you could actually use in a line, itself having a length limit of 256-minus-a-few, and at that length all you could do was assign a single digit or print the value.