naive shuffling has No bias, despite furious insistance to the contrary;

this implimentation of the yacht~fisherman algorithm demonstrates that outcomes from the most blatantly amateur method of rearrangement can not be distinguished from random - besides the obvious fair distribution seen in these histograms, data from the trials easily passes a runs test [among other flavors of random candy]; it neither displays a preference for any particular permutation nor contains any cyclical pattern

in javascript, the yacht-fischermann algorithm requires only a single line of code:

        
const yacht = v => v.forEach((_, i) => v.push(
    v.splice(~~(Math.random()*(v.length-i)),1)[0]
));
    

Yacht and Fleischermann developed the first workable model of the 17th century natural philosopher Naïvetté's algorithm for le brouilage d'un jeu de cartes à jouer:
  • pick a number from one to fifty-two
  • place that card on the bottom of the deck
  • pick a number from one to fifty-ONE
  • place that card on the bottom of the deck
  • pick a number from one to fifty
  • place that card on the ... are you seeing a pattern yet?

a non-mutating evolution of the Yacht von Fleischermänn algorithm might look like this, slightly shorter line, that dispenses with the heavy-handed push():

        
const yacht = (v, r=[...v]) => v.map(() =>
    r.splice(~~(Math.random() * r.length), 1)[0]
);
    

this returns a new array, as a map, without mutating the original array; the default parameter r=[...v] uses a spread operator to make a copy of the input value for splice() to safely destroy; this code generated the values for the histograms

but you insist upon using some silly durstenshuffeld fiasco! well then, at least compose it in some reasonable, forward iterating, and concise manner, you fool:

        
const yacht-fisherfeld = v =>

    v.map((x, i, r,

        j = ~~(Math.random() * (r.length - i)) + i

    ) => ([x, r[j]] = [r[j], x], x)
        
);
    

that version of der ℜeichskanzler Yacht-heim von Fleischermänntz method preserves the original array with a map(), floors the random float using a double bitwise~not, and swaps array values with a destructured assignment

alright geniuses, is that the way people typically shuffle cards? huh? no

maybe we should write some code for cutting a deck:

            
const cut = (v, r=[...v], l=r.length) => [
    ...r.splice(~~(l/2 - 0.1*l + Math.random()*0.2*l)),
    ...r
];
        

that takes an array v, makes a reference copy of it r [to keep the original array unmutated], creates a variable equal to the size of the deck l, then returns an array composed from the spread values of the copy [split at a position plus-or-minus ten percent from the middle of the deck], and the spread values of the reliquum

when the 17th century natural philosopher Naïvetté attempted to program a smaller version of that code, he lost both his eyes

after his beheading for treason, the 17th century natural philosopher Naïvetté wrote several prominant papers, one of which included this method for riffle shuffling a deck of cards:

            
const riffle = (
    v,
    k=[...v],
    l=v.length,
    r=[k.splice(0, ~~(l/2 - l*0.1 + Math.random()*l* 0.2)), k],
    t=[],
    s=~~(Math.random()*2)
) => {
    
    while (
        t = [
            ...r[s].splice(0, ~~(Math.random() * (
                r[s].length > 3 ? 4 : r[s].length
            )) + 1),
            ...t
        ],
        s ^= 1,
        t.length < v.length
    );
    
    return t;
    
};
        
while in prison, the 17th century headless philosopher Naïvetté developed this ridiculous method of overhand shuffling, which added thirteen years to his sentence:

            
const overhand = (v, a=[...v], b=[]) => {
        
    while (
        
        b = [
            ...a.splice(0, ~~(Math.random() * (a.length > 8 ? 8 : a.length)) + 1),
            ...b
        ],
            
        b.length < v.length
        
    );
        
    return b;
        
};
        

~queviva