Math.random() is not so random: The Illusion of Randomness in JavaScript
Don’t Trust Math.random(): Uncovering the Hidden Predictability of Random Numbers.
What is Math.random()?
Math.random() is one of the most used functions in JavaScript for generating random numbers. It returns a floating-point number in the range [0, 1) – but don’t be fooled by its name: the numbers it produces are not truly random. In fact, Math.random() uses a deterministic pseudorandom number generator (PRNG) under the hood. In this blog, I will dive into why “Math.random() is not random,” how it works internally, and the implications for developers. By the end, you’ll understand the limitations of Math.random and know when to avoid it.
This is a detailed recap of my recent talk at Git Togethers Bengaluru.
Randomness: Truth vs. Illusion
True randomness means unpredictability – outcomes that cannot be determined in advance. Computers, however, are deterministic machines; left to themselves, they can’t generate truly random numbers without an external random input. Instead, they rely on pseudorandom algorithms – deterministic processes that simulate randomness. As John von Neumann famously said, “Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin.” In other words, any purely algorithmic random number is inherently pseudo-random, not truly random.
Pseudorandom Number Generators (PRNGs): Algorithms that use an internal state and a deterministic update function to produce a sequence of numbers that “appear” random. Given the same starting state (seed), a PRNG will always output the exact same sequence of numbers. The sequence will eventually repeat because the state space is finite. The length of the sequence before it repeats is called its period. PRNG quality is judged by period length and statistical properties of its output – a high-quality PRNG produces output that is hard to distinguish from true randomness.
True Randomness (Hardware/Physical): Obtained from unpredictable physical processes, like electronic noise or quantum phenomena. For example, flipping a fair coin or sampling radioactive decay yields true random outcomes. In computing, true randomness usually comes via the operating system’s entropy sources (e.g. thermal noise, device interrupts timing) or dedicated hardware. We’ll later see a fun example of cloud security using lava lamps for randomness!
Key Point: Math.random(), like most software random functions, is a PRNG. It produces an illusion of randomness sufficient for many purposes, but it is not unpredictable in the cryptographic sense. Next, let’s peek under the hood to see how Math.random is implemented in JavaScript engines.
How Math.random() Works in JavaScript
The ECMAScript specification defines Math.random() in abstract terms: it returns a number ≥0 and <1, “chosen randomly or pseudo-randomly with approximately uniform distribution,” using an “implementation-dependent algorithm”. This means each JavaScript engine (Chrome’s V8, Firefox’s SpiderMonkey, Safari’s JavaScriptCore, etc.) can choose its own algorithm, as long as the output is uniformly distributed in [0,1). There’s no fixed formula in the spec, and importantly no guarantee of cryptographic security.
Historically, engines have used different PRNG algorithms for Math.random:
Early V8 (Chrome & Node.js): Until late 2015, Google’s V8 engine used a Multiply-With-Carry (MWC) algorithm called MWC1616. This algorithm maintained a 64-bit internal state split into two 16-bit parts, and on each call updated the state with linear formulas and combined them into a 32-bit result. It was fast and low-memory, but had serious limitations:
It only produced ~32 bits of randomness per call (32-bit precision) – meaning Math.random() in V8 could only return one of about 2^32 possible values, whereas a double-precision float could represent 2^52 different fractions in [0,1). In contrast, Firefox’s engine used the full 52 bits of the double for randomness. V8’s 32-bit output was a potential quality loss.
The period of MWC1616 was at most 2^32 outputs. In fact, instead of one large cycle, it had many shorter cycles – if the initial seed was unfortunate, it could fall into a cycle less than 40 million long. That sounds large, but it’s tiny compared to modern standards (and even smaller than the theoretical 2^64 state size).
It failed statistical tests for randomness. In other words, patterns could be detected in its output if you analyzed large sequences. As Donald Knuth warned, “old methods” of RNG can be “unsatisfactory” and get passed down blindly – MWC1616 was one such method that persisted too long.
Modern V8 (Chrome v49+ / Node.js): In 2015, a Node.js startup (Betable) discovered issues with Math.random (more on that story soon) and alerted the V8 team. V8 promptly switched to Xorshift128+ – a higher-quality PRNG with a 128-bit state and a period of 2^128–1. Xorshift128+ is much better: it passes rigorous statistical test suites (TestU01) and utilizes 128 bits of state for a huge period. Chrome 49 (and Node.js built on that V8) incorporated this fix, and interestingly Firefox and Safari also adopted Xorshift128+ around the same time. So today, all major JS engines use Xorshift128+ (or a variant) for Math.random().
SpiderMonkey (Firefox): Firefox long ago used a well-known PRNG (likely something like XorShift or Mersenne Twister) and historically already produced 53 bits of randomness (full double precision). In 2015, Firefox also moved to Xorshift128+ for consistency and quality.
JavaScriptCore (Safari): Safari’s engine similarly switched to Xorshift128+. Earlier, it might have used an LCG or something comparable. Now all are on par in using 128-bit state PRNGs.
What does this mean? Math.random() is now fairly high quality for general use – it’s fast and statistically passes tests – but it remains a PRNG. It is entirely deterministic given its internal state. In fact, on V8 you can actually set the seed for Math.random for reproducible results (for example, running Node.js with the flag --random_seed=42
will make Math.random() produce the same sequence every time, check the attached screen recording). This is great for debugging or demos, because it proves the point: same seed, same “random” numbers.
To summarize how it works internally now (using V8 as an example):
On startup, V8 initializes a seed for the PRNG. If not provided via a flag, it uses some source of entropy from the environment (often the OS). For example, it may use
/dev/urandom
on Linux or a similar high-entropy source to get a random seed. This might give a cryptographically random starting state, but only for seeding.Math.random() is implemented by generating numbers from that seed using the Xorshift128+ algorithm. V8 actually generates numbers in chunks (it keeps an “entropy pool” of 64 random values in memory, refilling it with new Xorshift outputs when it runs out, to reduce overhead of calling into C++ often). Each call to Math.random() takes the next number from this pool.
Each output is a 64-bit integer (from Xorshift128+) scaled to a floating-point between 0 and 1. Because it’s 64-bit, it effectively provides 52 random fraction bits (enough to fill a double’s precision).
If you rerun the program with the same seed, the sequence of Math.random() results will be identical. If you could somehow peek at the PRNG’s internal state, you could predict all future outputs. (Engines obviously don’t expose the state, but as we’ll see, attackers have found clever ways to infer it in some cases.)
Important: The current Math.random() algorithm (Xorshift128+) is not cryptographically secure. The engine developers themselves note this. Xorshift128+ is fast and has good statistical properties, but it’s a linear algorithm that can be cracked with sufficient outputs or analysis. The takeaway is: Math.random is fine for non-critical randomness (like animations, simple games, simulations), but it should never be used for security-sensitive randomness.
Limitations of Math.random()
Even with improvements, Math.random has inherent limitations because it’s a PRNG:
Deterministic Output: Given the same initial state, Math.random() will always produce the same sequence. This means it’s not truly random – it’s repeatable. If an attacker can figure out or guess the PRNG’s state, they can predict all future outputs. In older algorithms like MWC, the state could be deduced from a small sample of outputs (because a single output leaked 32 of the 64 state bits). Modern Xorshift128+ is harder to invert, but it’s still deterministic – predictable in principle. Determinism is great for debugging or simulations (you want repeatability there), but terrible for security or unpredictability.
Finite State and Period: A PRNG has a fixed-size internal state (e.g. 128 bits). This bounds how many distinct values it can produce before cycling. “The number of distinct values that can be generated from a pseudo-random sequence is bounded by the sequence’s cycle length.” In other words, if the PRNG has a period of N, it can output at most N different random numbers (or N different sequences of a given length) before repeating patterns. With Xorshift128+, the period is ~3.4×10^38 – astronomically large, so hitting a full cycle isn’t a concern in practice. But with weaker PRNGs like the old MWC (period ≤ 4.3×10^9), it was conceivable to cycle within a long-running application. In fact, any combined random values (like generating multiple random components to form an ID or password) are limited by the PRNG’s state. If the state is small, your combined space of outcomes is much smaller than you might think.
Limited Entropy / Precision: As noted, older implementations didn’t even use the full available precision. V8’s MWC1616 only produced 32 random bits per Math.random(). That means it could only ever return 4.29 billion possible distinct floating-point values. If you used multiple Math.random calls to build a larger random value (say, a 128-bit ID), you weren’t actually spanning the full 2^128 space – you were limited by the generator’s 2^32 cycle. This led to a false sense of security in some cases (we’ll see an example). Modern engines now use ~52 bits per call, so this specific limitation is eased, but it’s worth remembering that the output entropy is bounded by the PRNG’s state.
Non-Uniformity or Bias: A well-designed PRNG like Xorshift128+ is statistically uniform for most needs, but bad PRNGs can have biases. For example, if a PRNG has a short cycle of length M, any generated numbers that require more than M states will omit some combinations entirely. Extreme example: our simple linear example might only generate even numbers, or alternate in a pattern – obviously biased. MWC1616 had subtle correlations between higher and lower bits (the high half of its output was mostly dependent on one part of state), making some bits “less random” than others. The bottom line: quality matters – a poor-quality PRNG can fail tests (like failing to produce all 8-bit byte values equally often, etc.). Fortunately, post-2015 Math.random implementations fixed major biases (passing TestU01 battery), but older ones did not.
Not Cryptographically Secure: This deserves repetition. No matter how uniform or long-period the output is, if it’s deterministic and predictable, it’s not secure. Cryptographic security requires that an attacker cannot predict future output even if they know how the algorithm works and have seen some past outputs. Math.random is not designed to meet this criterion. In fact, it explicitly fails if you throw serious analysis at it. We’ll discuss concrete security failures next.
Case Study: The Betable Collision Incident
Let’s illustrate these limitations with a real-world story. In 2013, engineers at Betable (an online gaming platform) built a distributed system where each request was assigned a random 22-character identifier (using [A-Za-z0-9] characters, effectively base-64). That’s like generating a random 132-bit number for each request. The space of 22-char IDs is huge: 64^22 ≈ 2^132 possibilities. By the birthday paradox math, even generating millions of IDs per second for centuries, a collision (duplicate ID) should be practically impossible. They used Node.js’s Math.random() to generate these IDs, assuming it was “random enough”.
To their surprise, “the impossible happened” – they got a duplicate ID in production! This was an alarming moment: either their math was wrong or their random numbers weren’t as random as assumed. It turned out to be the latter. V8’s PRNG (MWC1616) was the culprit. Because it only had a 2^32 period and 32-bit precision, it drastically limited the pool of unique 22-char strings that could be generated. Essentially, instead of 2^132 possibilities, they were effectively limited to around 2^32 possible outputs, meaning collisions were way more likely than expected (1 in 2^32 is about 1 in 4 billion chance for each new ID to repeat an old one – not trivial at high throughput). The Betable team admitted it was “stupid” not to understand V8’s PRNG limitations earlier. They had unknowingly pushed Math.random beyond its quality guarantees.
How was it resolved? Betable immediately switched to a CSPRNG (Cryptographically Secure PRNG) for their IDs. In Node.js, they used crypto.randomBytes()
(which under the hood uses OpenSSL’s secure RNG drawing from the OS). With a cryptographic RNG, the chance of collisions returned to the astronomically low probability they expected – and no further collisions occurred. This incident prompted a wider discussion in the JS community. Betable’s CTO Mike Malone wrote a detailed post “TIFU by using Math.random()” exposing how V8’s Math.random was “comparatively unsatisfactory (arguably completely broken)” and urging for a better algorithm. The V8 team took this feedback seriously. Within a few weeks (in late 2015), V8 replaced MWC1616 with Xorshift128+ to improve Math.random’s quality. Chrome’s release notes for version 49 even credit this change, and other browsers followed. So this story has a happy ending: developers learned to be careful with PRNG assumptions, and JS engines got better RNGs as a result.
Lesson: Math.random might be fine for picking a random color or shuffling a UI element, but don’t use it for generating IDs, unique tokens, or anything that requires a large collision-free space. The Betable case shows how a PRNG’s finite state can wreck your assumptions. If you truly need billions upon billions of unique random values, use a secure or high-quality generator with sufficient entropy.
Predictability and Security Risks
The Betable example was about collisions (statistical quality). Now let’s talk about an even more dangerous aspect: predictability. If an adversary can predict your “random” values, they can exploit that in many ways (guessing random tokens, winning games of chance, impersonating users, etc.). Math.random’s predictability has been exploited in the wild:
Online Gambling Hack (CSGOJackpot): One famous incident involved a betting site for CS:GO game skins. In 2015, CSGOJackpot was using Node.js and Math.random() to pick a winner for each jackpot round. The server would generate a random “winning percentage” between 0 and 1 with Math.random() and use it to select the winning ticket. A security researcher discovered this and realized Math.random() in Node (V8) was the old MWC PRNG. Crucially, the site publicly revealed the 16-digit winning percentage after each round (for “fairness” verification). With two or three published outputs, the researcher reverse-engineered the PRNG state and could then predict the next round’s winning number! In fact, given two consecutive outputs of MWC, one can brute-force the remaining state in seconds (because 32 bits of state are exposed per output, leaving only 32 unknown bits – ~4 billion possibilities, which is feasible to search with a computer in a short time). The researcher demonstrated the hack in a live stream: he would tell viewers the next winning number before the round ended. This caused quite a stir on Reddit and forced the site to fix their RNG (they moved to a secure method). The TL;DR: Using Math.random() for a lottery was “trivial to predict” given a couple of outputs, enabling a major cheating vulnerability.
Security Tokens & Passwords: While perhaps less dramatic, many web developers have mistakenly used Math.random() to generate things like password reset tokens, API keys, session IDs, or one-time passwords. This is extremely dangerous because attackers can either guess these values or, worse, derive the sequence if they can collect some samples. For instance, if an application’s session IDs are based on Math.random, an attacker who sniffs or observes a few session IDs might predict future ones and hijack other sessions. In one documented case, an attacker exploited an application’s use of Math.random for 2FA codes, allowing them to bypass 2FA by predicting the codes (since the PRNG sequence was not truly random). Another example: the crypto-js library (popular for encryption in JS) was reported for using Math.random as its entropy source for random IVs/nonces – a glaring weakness since it made encryption easier to break. These incidents underline that Math.random is not safe for security-critical randomness.
Predictability with Enough Data: Even if an attacker can’t directly grab the seed, many non-cryptographic PRNGs (like linear or xorshift ones) can be cracked if you observe enough output bits. Research has shown that Xorshift128+ can be solved for its state with a few hundred output bits using algebraic techniques (because it’s a linear recurrence in GF(2)). That means if someone could somehow collect, say, 128–256 bits of your Math.random outputs (which is not too many numbers) under certain conditions, they could compute the internal state and predict all future outputs. It’s not trivial, but it’s possible in theory. With cryptographic PRNGs (like those based on AES or SHA), this is computationally infeasible – that’s the difference.
Bottom line: If you use Math.random() for anything that adversaries might exploit (games, security tokens, randomized challenges), you are risking that it can be predicted. A deterministic algorithm “can and will be broken” when used inappropriately. The common wisdom in security circles is very clear: “it’s a bad idea to use Math.random() for security related tasks”. Instead, use dedicated secure RNGs (next section). Even for gambling or lotteries, use a cryptographic RNG so no one can rig the outcome.
Better Alternatives for Randomness
If Math.random() isn’t random enough for your needs, what are the options? Thankfully, both the browser and Node.js provide cryptographically secure random APIs:
Web Crypto API (Browsers): Modern browsers support
window.crypto.getRandomValues()
. This function fills a typed array with random bytes generated from a cryptographically secure PRNG. Under the hood, the browser’s engine will pull from the operating system’s high-quality entropy source or use hardware RNGs, so the output is unpredictable. For example,crypto.getRandomValues(new Uint8Array(4))
might give something like[137, 32, 62, 244]
– 32 bits of random data. This API is widely supported across browsers. It’s the go-to for things like generating secure tokens, UUIDs, or crypto keys in front-end code. Important: The values from getRandomValues are not just “more random” – they are cryptographically random, meaning even if someone knows every detail of the algorithm, they can’t predict the output without breaking low-level cryptographic assumptions. In practice, that means these numbers might come from something like /dev/urandom and be additionally stirred with strong algorithms. As a developer, you can trustcrypto.getRandomValues
for security.Node.js Crypto module: In Node, you have the built-in
require('crypto')
module which provides several methods. Common ones arecrypto.randomBytes(size)
, which gives a Buffer of secure random bytes, and higher-level helpers likecrypto.randomInt(max)
for a secure random integer, orcrypto.randomUUID()
(as of recent Node versions) which directly gives a RFC4122 version-4 UUID string generated with proper randomness. Node’s crypto.randomBytes is backed by OpenSSL’s RNG, which regularly reseeds from the OS and uses algorithms like SHA-1 or AES internally. It’s designed for cryptographic strength. The performance is usually a bit slower than Math.random (because gathering secure entropy is work), but it’s quite decent (modern systems can generate megabytes of secure random data per second). In most use cases, you won’t notice the difference for something like generating a few tokens. As a rule: Usecrypto
in Node whenever randomness has any security or uniqueness importance. For example, if you need a random API key of 32 characters, docrypto.randomBytes(24).toString('hex')
rather than trying to use Math.random.UUID generation: UUIDs are often used as unique identifiers. The version-4 UUID is essentially 122 bits of random data plus some fixed bits. It’s tempting to generate those by calling Math.random() a few times – don’t! While the probability of collision might still be low, using a secure source ensures unpredictability as well as uniqueness. Many languages had issues when UUID generation wasn’t using secure randomness. For JavaScript, you can use the
crypto.randomUUID()
in recent environments, or use a library likeuuid
which by default uses crypto APIs if available. This ensures your UUIDs are generated from strong entropy. It’s worth noting that even non-cryptographic randomness might give you a unique ID, but if that ID is meant to be secret (like an invitation code or a password reset link), then predictability matters — and only CSPRNG will give you that unpredictability.Fallback for Older Environments: If you ever find yourself in an environment where secure APIs aren’t available (for example, an older browser without Web Crypto), the last resort might be to use a user-provided seed or a better PRNG algorithm implemented in JavaScript (like a userland Mersenne Twister or a well-vetted library). One could use a library such as seedrandom which allows seeding and uses better algorithms than the built-in Math.random. But caution: these are still not cryptographically secure, they’re just better or allow reproducibility. Some libraries, like the Shifty library mentioned by deepsource, will try to use crypto.getRandomValues and only fall back to Math.random if absolutely necessary. In any case, prefer native secure APIs when available.
Performance Consideration: Secure randomness is slightly slower. Math.random can be extremely fast (especially with the caching/pool mechanism – millions of calls per second). Cryptographic RNGs might be an order of magnitude slower, but still on the order of say tens of millions of bytes per second, which is usually fine for most applications. Unless you’re generating huge streams of random data continuously, the security trade-off is worth it. And if you are, you probably know to use a dedicated RNG stream anyway. In browsers,
getRandomValues
is quite efficient too (it might gather entropy in chunks).Reproducibility vs Security: Sometimes you want a pseudorandom sequence you can replay (for example, in unit tests or simulations, you might seed an RNG to get predictable behavior). In such cases, you obviously wouldn’t use a secure random (which you can’t control the seed of easily). Instead, you can use a userland PRNG. For example, if writing a game where you want a “random seed” so that players can replay a world, you might implement a simple PRNG (like an LCG or Xorshift) yourself, or use an npm package that supports seeding. That’s fine – just remember to never use those seedable PRNGs for security or secret data. Use them for things like procedural generation in games, not for generating auth tokens. If you need both reproducibility and safety (rare combination), you could always generate a random seed with a secure RNG and then use it to seed your game PRNG – that way each run is different but you could record the seed to replay, while initial seed is unpredictable.
Other Languages / Systems: Just as an FYI, this issue isn’t unique to JavaScript. Many languages have a default PRNG that is not cryptographically secure (e.g., Java’s
java.util.Random
, Python’srandom
module, etc.). They often provide a separate secure RNG (Java hasSecureRandom
, Python hassecrets
module oros.urandom
). JavaScript’s situation is a bit unique in that Math.random is built into the language and historically was hard to seed manually, but now we have the Web Crypto API for secure needs. The principle remains: know your tools – if you need true randomness, reach for the crypto-grade APIs.
The Quantum Question: Randomness in the Future
You might wonder, “Can a powerful computer (or a quantum computer) make Math.random more random or predict it faster?” It’s an interesting thought. Quantum computers are often discussed in context of breaking cryptography, but when it comes to randomness:
A quantum computer doesn’t magically turn a predictable algorithm into an unpredictable one. Math.random would still be a deterministic algorithm. In fact, if a PRNG is weak, you don’t even need a quantum computer to crack it (as we saw, a normal PC can crack MWC or even Xorshift with enough data). If a PRNG is strong (cryptographically), even a quantum computer would struggle because there’s no efficient shortcut known for predicting truly secure random outputs (quantum might give at best a square-root speedup in brute force via Grover’s algorithm, but if you’re dealing with 128-bit security, that’s still 2^64 operations – impractical).
On the flip side, quantum mechanics can be a source of true randomness. Quantum Random Number Generators (QRNGs) use physical quantum phenomena (like the decay of a photon, or quantum vacuum fluctuations) to produce random bits. These are increasingly being used in high-security contexts. For example, some companies have USB devices that generate random numbers from quantum effects. If someday our computers come with built-in quantum entropy devices, that could feed our CSPRNGs for even better randomness. But from a developer standpoint, you’d still just call a secure API; the “quantumness” is under the hood.
There’s a fun example in the real world: combining classical chaos and unpredictability. One famous implementation is Cloudflare’s Lava Lamp wall. They have a camera observing a wall of lava lamps and use the pixel data as a source of entropy to seed their random number generator for cryptography. It’s essentially leveraging the unpredictable physics of lava lamp blob movements as randomness.
Cloudflare’s "Wall of Entropy" – a wall of lava lamps whose chaotic movements are turned into random numbers for cryptographic use. Physical processes like this produce true randomness, unlike the deterministic Math.random().

The lava lamp approach (nicknamed LavaRand) is a clever reminder that randomness ultimately often comes from nature. Other sources include atmospheric noise (e.g., random.org uses radio noise), or thermal noise in circuits. In quantum terms, even simply measuring a quantum particle’s spin yields a random outcome. So quantum computing isn’t so much solving the Math.random problem as it is providing new ways to generate true randomness. We might see “quantum random APIs” in the future, but under the hood they’ll likely feed into the same kind of secure random functions we discussed.
In summary, randomness is fundamentally a hardware/domain issue: you need entropy from somewhere. Math.random doesn’t have access to magical entropy; it’s a closed algorithm. Quantum or not, if you need randomness, you must go outside the algorithmic box – to the OS, to hardware, to physics.
Conclusion
We’ve seen that Math.random() is definitely not truly random – it’s a PRNG with specific internal algorithms and limitations. For about 80% of use cases (non-security, small-scale randomness needs), Math.random is fine – it’s fast and now of decent quality statistically. But for the other 20% of cases where randomness really matters – security tokens, significant money or data at stake, stringent uniqueness requirements – you should not rely on Math.random(). Instead, use the available cryptographic random APIs or libraries, which are designed to be unpredictable and robust.
To keep a light tone: using Math.random for crypto is like using a rubber sword in a real battle – it just won’t hold up! As developers, part of our responsibility is to know the tools and their appropriate use. Math.random is a handy tool, but it’s not a one-size-fits-all. There’s a saying: “Don’t roll your own crypto.” Similarly, don’t roll your own random if it’s for crypto either – use the primitives provided by experts.
Key Takeaways:
Math.random uses a pseudorandom algorithm (currently Xorshift128+ in modern JS engines) with a fixed state; it is deterministic and periodic, not truly random.
Because of that, it can produce duplicates in large outputs and can be predicted or reversed by attackers in some scenarios (as shown by real incidents).
For casual randomness (e.g., UI effects, simple games), Math.random is usually sufficient. But always be aware of its limitations – e.g., don’t assume “astronomical” odds without considering the PRNG’s true entropy.
For anything requiring security or strong uniqueness, use cryptographic randomness:
crypto.getRandomValues
in the browser, or Node’s crypto module. These draw from true entropy and are designed to be unpredictable.If you need reproducible sequences (for tests or simulations), use a seedable PRNG library rather than Math.random, since Math.random can’t be seeded manually in a standard way. There are many well-known algorithms (Mersenne Twister, etc.) implemented in JS if needed.
Quantum computers don’t change the fundamentals of randomness – you still need entropy. In fact, quantum phenomena can be great entropy sources, but your code will still use APIs to get those random numbers if available. Always keep an eye on future developments in random number generation – as computing evolves, we might get even better sources (perhaps someday
Math.quantumRandom()
? 😉).
This is a detailed recap of my recent talk at Git Togethers Bengaluru. Big thanks to everyone who joined and made it engaging!