Menu

C# Random Numbers: Random.Next, Ranges, Seeds, Shuffle and Secure Random

How to generate random numbers in C# with the Random class: Next and its exclusive upper bound, random doubles in a range, seeds for reproducible results, picking a random element, shuffling a list, the new Random in a loop pitfall, and cryptographically secure numbers.

This page includes runnable editors - edit, run, and see output instantly.

The System.Random class generates pseudo-random numbers: a deterministic sequence that looks random, starting from a seed. Create one instance and call its methods:

Example output:

1294270287
83
You rolled a 5
0.6060

Run it again and the numbers change. The upper bound is exclusive: Next(1, 7) can return 1, 2, 3, 4, 5 or 6, never 7. For a number between 1 and 10 inclusive, write Next(1, 11). Passing a max smaller than min throws an ArgumentOutOfRangeException.

Random numbers in a range

Next(min, max) covers integers. For a double in a range, scale NextDouble(); for a decimal such as a price, build it from integers so the result has the precision you want:

Example output:

23.6 C
72.42
heads
N
-3

rng.Next(100, 10000) / 100m picks a whole number of cents and divides by a decimal, so the price always has exactly two decimals. Scaling a double would give values like 72.4183.

Seeds: reproducible sequences

Passing an integer seed makes the sequence repeatable. Two Random objects with the same seed produce exactly the same numbers, in the same order:

Output:

16 29 91 99 91
16 29 91 99 91
38

Seeds are what you want for tests, simulations you need to rerun, replays in games, and procedurally generated levels ("world seed 2026"). Log the seed you used, and a surprising result can be reproduced later.

Microsoft does not promise that a seed produces the same sequence on every .NET version. Do not save seeded output as data (for example, generating customer IDs from a fixed seed and expecting to regenerate them after an upgrade).

Picking a random element

A random index is Next(0, count), which is always a valid index because the upper bound is exclusive:

Output:

Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common     695
rare       260
legendary  45

The weighted draw maps a number from 0 to 99 onto ranges whose sizes are the weights: 0 to 69 is common, 70 to 94 rare, 95 to 99 legendary. Over 1,000 draws the counts land near 700, 250 and 50 (here 695, 260 and 45).

Shuffling a list: Fisher-Yates

To put a list in random order, walk from the end and swap each element with a random element at or before it. This is the Fisher-Yates shuffle; it is fast and every ordering is equally likely. Written as a generic method, it works for arrays and lists of any element type:

Output:

5 A 8 6 4 7 2 3
4 2 1 3 5

Two shortcuts you will see online are worse. list.OrderBy(x => rng.Next()) works but sorts, so it is slower, and random keys can collide. Swapping with rng.Next(items.Count) instead of rng.Next(i + 1) looks similar but makes some orders more likely than others. On .NET 8 and later, Random.Shared.Shuffle(array) does a correct shuffle in place, and rng.GetItems(choices, 5) picks five random items (with repetition).

The new Random() in a loop pitfall

A classic bug is creating a new Random every time a number is needed:

// Don't do this
int RollDie()
{
    var rng = new Random();   // new instance on every call
    return rng.Next(1, 7);
}

On .NET Framework, new Random() without a seed uses the system clock (Environment.TickCount), which changes only every 10 to 16 milliseconds. Calls made in a quick loop create instances with the same seed and return the same number, so a loop of dice rolls prints 4 4 4 4 4. .NET Core and .NET 5+ seed every instance from a shared random source, which hides the symptom, but creating an object per number is still wasteful and the code breaks when it is reused in older projects or Unity.

The fix is one instance for the class, stored in a field:

Output:

3 6 6 5 6 1 2 1

The seed here is only so the example prints the same thing every run; in a real game you would write new Random().

Threads and Random.Shared

Random is not thread-safe. If two threads call Next on the same instance at the same time, its internal state can be corrupted; the well-known symptom is an instance that returns 0 from then on. Options, from simplest:

static class Dice
{
    // .NET 6 and later: a thread-safe shared instance
    public static int RollShared() => Random.Shared.Next(1, 7);

    // Any version: one instance per thread
    [ThreadStatic] private static Random _local;
    private static Random Local => _local ?? (_local = new Random(Guid.NewGuid().GetHashCode()));
    public static int RollPerThread() => Local.Next(1, 7);

    // Any version: a lock around one shared instance
    private static readonly Random _rng = new Random();
    private static readonly object _sync = new object();
    public static int RollLocked()
    {
        lock (_sync) { return _rng.Next(1, 7); }
    }
}

Random.Shared is the right default in new code: it is thread-safe, needs no field, and is seeded randomly. It cannot be seeded, so use your own new Random(seed) when you need reproducibility.

Cryptographically secure random numbers

System.Random is predictable by design. Anyone who knows the seed, or sees enough outputs, can compute the rest. That is fine for games and sampling, and wrong for passwords, reset tokens, session IDs, keys and lottery draws. For those, use RandomNumberGenerator from System.Security.Cryptography, which reads from the operating system's secure generator:

Example output:

Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF

RandomNumberGenerator.GetInt32 (available on .NET Core 3.0 and later) returns an unbiased integer in the range. Do not build secure codes with bytes[0] % 10: the modulo makes some digits more likely than others. On .NET 6 and later, RandomNumberGenerator.GetBytes(16) returns a new array directly, and Convert.ToHexString(bytes) formats it. For unique identifiers where secrecy does not matter, Guid.NewGuid() is simpler.

Frequently Asked Questions

How do I generate a random number in C#?

Create one Random object and call Next: var rng = new Random(); int roll = rng.Next(1, 7); gives 1 to 6. Next(max) gives 0 to max - 1, and NextDouble() gives a double from 0.0 up to but not including 1.0. On .NET 6 and later you can skip the object and use Random.Shared.Next(1, 7).

Is the upper bound of Random.Next inclusive?

No. Next(min, max) returns a number greater than or equal to min and strictly less than max. For a number from 1 to 10 inclusive, write Next(1, 11). This makes Next(0, list.Count) a valid index into a list.

Why does new Random() give the same numbers?

On .NET Framework, new Random() is seeded from the system clock, which only changes every few milliseconds, so several instances created in a quick loop get the same seed and the same sequence. .NET Core and .NET 5+ seed each instance differently, but the fix is the same everywhere: create one Random and reuse it.

How do I get the same random numbers every time in C#?

Pass a seed to the constructor: new Random(42). Two instances with the same seed produce the same sequence, which makes tests, simulations and procedural generation reproducible. The sequence for a given seed can differ between .NET versions, so do not store seeded output as permanent data.

Is System.Random secure enough for passwords or tokens?

No. Random is predictable: anyone who learns its state or seed can reproduce the numbers. For passwords, tokens, keys and anything security-related, use System.Security.Cryptography.RandomNumberGenerator, for example RandomNumberGenerator.GetInt32(0, 10) or RandomNumberGenerator.GetBytes(32).

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED