I thought that const and let were hoisted just like var, but this leads me to believe I am wrong.
Not really, let and const are indeed hoisted, or as I like to call it, half-hoisted. There are two big differences between var foo and let foo: Scope and initialization. You already know about the scope difference. The second one is that with var foo, both the declaration and the initialization (with undefined) of foo are hoisted. With let, only the declaration of foo is hoisted, not the initialization. foo is only initialized when the step-by-step execution of the code reaches the let foo statement. You can't use (read from or write to) an un-initialized identifier. This time during which you can't use the identifier is called the Temporal Dead Zone (TDZ).
Even with var, the initialization that's hoisted is the initialization with undefined, not the value on the right-hand side of the =:
console.log(typeof foo); // "undefined"
foo();                   // TypeError: foo is not a function
var foo = () => {};
 
 
The change you've made, moving the declaration of getRandom up before the first use of it, is the correct thing to do. (Or use a function declaration, since the declaration as a whole [including the creation of the function] is hoisted.)
Let's look at this half-hoisting thing:
let foo = "outer";
function x()
{
    console.log("...");
    let foo = "inner";
    console.log(foo);
}
x();
 
 
(let and const have block scope, but I'm using a function because I'll be constrasting with var in a moment.)
Within x, that inner foo can't be used until the let foo line. But, you can't access the outer foo above it; this fails:
let foo = "outer";
function x()
{
    console.log(foo);    // ReferenceError: `foo` is not defined
    let foo = "inner";
    console.log(foo);
}
x();
 
 
That's the half-hoisting: The declaration of the inner foo is hoisted, but the variable isn't initialized until the let foo statement. That means you can't use foo at all (not even from a containing scope) above the let foo line. The inner foo shadows the outer foo throughout the function, but you can't use it until it's initialized. This is covered in Let and Const Declarations in the spec.
This is in constrast with var:
var foo = "outer";
function x()
{
    console.log(foo);    // undefined
    var foo = "inner";
    console.log(foo);    // "inner"
}
x();
 
 
That runs just fine because both the declaration and the initialization of foo (with undefined) are hoisted to the top of the function. (After hoisting, the var foo = "inner"; line becomes a simple assignment statement.) So the inner foo shadows the outer foo throughout, and is also accessible throughout, initially with its default value (undefined) and the later with "inner" (once that's been assigned to it).
Since the TDZ is temporal (related to time), not spatial (related to space or location within the scope), you can use an identifier created by let or const (or class) above its declaration, just not before its declaration. This fails because getNumber tries to access theNumber before it's initialized, while it's still in the TDZ:
const getNumber = () => theNumber;
console.log(getNumber()); // ReferenceError
let theNumber = 42;
 
 
This works because getNumber accesses theNumber after it's initialized:
const getNumber = () => theNumber;
let theNumber = 42;
console.log(getNumber()); // 42