Skip to main content

Random

These methods extend System.Random so you can pick an item, deal a hand, shuffle a list, or make a random price, date, string, enum value or GUID in one call. Use them for test data, samples, demos and games. They are not for security: none of them is cryptographically secure.

All methods live in the System namespace, so you do not need a using directive.

At a glance​

MethodWhat it does
NextItemPicks one item from a list, or one of two or more values.
NextItemsPicks a number of distinct items (no repeats), in random order.
NextBoolFlips a fair coin, or returns true with a given probability.
NextDoubleReturns a double in [min, max).
NextDecimalReturns a decimal in [min, max).
NextGaussianReturns a normally distributed value (mean 0 and standard deviation 1, or your own).
NextStringReturns a string of random letters and digits, or of characters from your alphabet.
NextEnumPicks one declared member of an enum.
NextGuidReturns a version-4-shaped Guid built from the Random, repeatable for a seed.
NextDateTimeReturns a DateTime in [min, max) with tick precision, keeping the Kind of min.
NextTimeSpanReturns a TimeSpan in [min, max) with tick precision.
ShuffleShuffles any IList<T> in place (Fisher–Yates).

Common rules​

  • Every method draws only from the Random you call it on. Pass new Random(seed) and you get the same results on every run (see Determinism with a seed).
  • A null receiver throws ArgumentNullException with the parameter name random. A null list, array or alphabet throws ArgumentNullException with its own parameter name.
  • Ranges are half-open: the lower bound is included and the upper bound is excluded. When the two bounds are equal, the method returns the lower bound and does not throw. One exception: NextDecimal with two equal bounds that use all 28–29 digits of decimal can return a value one unit below the bound, and at decimal.MaxValue or decimal.MinValue it can throw OverflowException (see NextDecimal).
  • An upper bound below the lower bound throws ArgumentOutOfRangeException with the parameter name maxExclusive.
  • The example values on this page come from the stated seed on .NET 10. A different seed, runtime or library build gives different values.
Not for secrets

System.Random is predictable. Do not use these methods for passwords, tokens, reset codes or ids that must be hard to guess. Use System.Security.Cryptography.RandomNumberGenerator for those.

Picking items​

NextItem​

NextItem picks one item, with each item equally likely. Pass an IReadOnlyList<T> (an array, a List<T> and most collections qualify), or pass two or more values directly.

Pick a reviewer or a status
string[] reviewers = { "Alice", "Bob", "Carmen", "Dmitri" };
string reviewer = new Random(42).NextItem(reviewers); // → "Carmen"

string status = new Random(42).NextItem("Pending", "Paid", "Shipped"); // → "Shipped"

The value overload has two named parameters before the params array: NextItem(T first, T second, params T[] others). This is on purpose. With a single params T[], the call random.NextItem(list) would bind with T set to the list type and return the list itself.

Empty input

An empty list throws ArgumentException with the parameter name items ("There are no items to pick from."). In the value overload, a null others array throws ArgumentNullException with the parameter name others.

NextItems​

NextItems(items, count) picks count different items: no item is picked twice. The result is in random order. Use it to deal cards, choose a sample of orders for an audit, or pick winners. The source list is not changed.

Pick three products to feature
List<string> skus = new List<string> { "SKU-100", "SKU-200", "SKU-300", "SKU-400", "SKU-500" };

IReadOnlyList<string> featured = new Random(42).NextItems(skus, 3); // → ["SKU-400", "SKU-200", "SKU-300"]
// skus is unchanged: ["SKU-100", "SKU-200", "SKU-300", "SKU-400", "SKU-500"]

A count of 0 returns an empty list. A count equal to the list size returns every item in a random order.

"Distinct" means distinct positions. If the source list holds the same value twice, the result can hold it twice.

Count out of range

A negative count, or a count greater than the number of items, throws ArgumentOutOfRangeException with the parameter name count.

Booleans and numbers​

NextBool​

NextBool() flips a fair coin. NextBool(probabilityOfTrue) returns true with the given chance, from 0 (never) to 1 (always). Use it to turn a feature on for a share of users, or to mark about one order in ten as discounted.

Coin flips and weighted flags
Random random = new Random(42);
bool[] flips = Enumerable.Range(0, 5).Select(_ => random.NextBool()).ToArray();
// → [true, false, false, true, false]

Random weighted = new Random(42);
int discountedOrders = Enumerable.Range(0, 1000).Count(_ => weighted.NextBool(0.1)); // → 111

bool never = new Random(42).NextBool(0); // → false
bool always = new Random(42).NextBool(1); // → true
Probability out of range

A probability below 0, above 1, or double.NaN throws ArgumentOutOfRangeException with the parameter name probabilityOfTrue.

NextDouble​

NextDouble(minInclusive, maxExclusive) returns a uniformly distributed double in [minInclusive, maxExclusive). The built-in Random.NextDouble() without arguments still returns a value in [0, 1).

A random latitude
double latitude = new Random(42).NextDouble(-90.0, 90.0); // → 30.25916386407762
double fixedValue = new Random(42).NextDouble(3.5, 3.5); // → 3.5

The result never equals maxExclusive. If floating-point rounding would land on it in a very small range, the method returns minInclusive.

Invalid bounds

A minInclusive that is NaN or infinite throws ArgumentOutOfRangeException with the parameter name minInclusive. A maxExclusive that is less than minInclusive, NaN or infinite throws ArgumentOutOfRangeException with the parameter name maxExclusive.

NextDecimal​

NextDecimal(minInclusive, maxExclusive) returns a uniformly distributed decimal in [minInclusive, maxExclusive). Use it for prices, totals and balances.

A random order total
decimal total = new Random(42).NextDecimal(5m, 500m); // → 335.712700626213290m
decimal price = Math.Round(new Random(42).NextDecimal(5m, 500m), 2); // → 335.71m

The randomness comes from one double, so the result has about 15 significant digits of resolution, not the full 28–29 digits of decimal. Round the result to the scale you need, as in the example.

The method does not overflow on very wide ranges. NextDecimal(decimal.MinValue, decimal.MaxValue) works. Equal bounds are the weak spot, because the result is computed by blending the two bounds and rounding. When both bounds use all 28–29 significant digits (for example 12345678901234567890123456789m), some draws return a value one unit below the bound. When both bounds are decimal.MaxValue, or both are decimal.MinValue, rounding can push the result past the limit, and the call throws OverflowException for some draws.

Invalid bounds

A maxExclusive less than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive.

NextGaussian​

NextGaussian() returns a sample from the standard normal distribution: mean 0, standard deviation 1. NextGaussian(mean, standardDeviation) shifts and scales it. Use it for values that cluster around a typical value, such as response times, heights or basket sizes. It uses the Box–Muller transform.

Normal samples
double z = new Random(42).NextGaussian(); // → 0.940176481011304
double height = new Random(42).NextGaussian(170, 10); // → 179.40176481011304
double latencyMs = new Random(42).NextGaussian(mean: 500, standardDeviation: 120); // → 612.8211777213564

double constant = new Random(42).NextGaussian(42, 0); // → 42

A standard deviation of 0 always returns the mean.

The result is not clamped

A normal sample can be any real number. NextGaussian(500, 120) can return a negative latency. Clamp the result yourself, for example with Math.Max(0, value), when negative values make no sense.

Invalid standard deviation

A negative or NaN standardDeviation throws ArgumentOutOfRangeException with the parameter name standardDeviation.

Text, enums and ids​

NextString​

NextString(length) returns a string of ASCII letters and digits (A–Z, a–z, 0–9). NextString(length, alphabet) draws each character from your own alphabet instead.

Random codes
string couponCode = new Random(42).NextString(8); // → "pIHgKQsf"
string pin = new Random(42).NextString(6, "0123456789"); // → "611512"
string empty = new Random(42).NextString(0); // → ""

Each character of the alphabet is equally likely. A character that appears twice in the alphabet is twice as likely.

Invalid arguments

A negative length throws ArgumentOutOfRangeException with the parameter name length. An empty alphabet throws ArgumentException and a null alphabet throws ArgumentNullException, both with the parameter name alphabet.

Not for passwords or one-time codes

Anyone who can guess or recover the seed can predict a code made by NextString. Use it for test data and display codes, not for anything a user must keep secret.

NextEnum​

NextEnum<T>() picks one of the members declared by the enum T, each declaration equally likely.

A random weekday
DayOfWeek deliveryDay = new Random(42).NextEnum<DayOfWeek>(); // → DayOfWeek.Thursday

The method picks from declarations, not from distinct values. If two members share a value, that value is picked twice as often. For a [Flags] enum, it returns only declared members; a combination such as Read | Write comes back only if the enum declares it as a member.

Enum without members

An enum that declares no members throws InvalidOperationException ("Enum '...' declares no members.").

NextGuid​

NextGuid() fills 16 bytes from the Random and sets the version and variant bits of a version 4 GUID. The result looks like a Guid.NewGuid() value, but a seeded Random gives the same id on every run. Use it for stable ids in test fixtures and snapshots.

Stable test ids
Guid customerId = new Random(42).NextGuid(); // → 96ba173e-04ae-4bcd-9986-9e56f0adbf3a
bool sameEveryRun = new Random(42).NextGuid() == new Random(42).NextGuid(); // → true

In the "D" format, the character at index 14 is always 4 and the character at index 19 is always one of 8, 9, a or b.

Guessable by design

These GUIDs are only as unpredictable as the Random that made them. Do not use them for session ids, API keys or links that grant access. Use Guid.NewGuid() for real ids.

Dates and durations​

NextDateTime​

NextDateTime(minInclusive, maxExclusive) returns a DateTime in [minInclusive, maxExclusive) with tick precision. The result has the DateTimeKind of minInclusive.

A random order date in 2024
DateTime yearStart = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime yearEnd = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);

DateTime placedAt = new Random(42).NextDateTime(yearStart, yearEnd);
// → 2024-03-22T18:47:14.0731989Z (Kind = Utc)

The method does not read the system clock. If you want "some time in the last 30 days", compute the bounds from a now value you pass in.

Invalid bounds

A maxExclusive earlier than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive. The comparison uses ticks and ignores Kind, so pass both bounds in the same kind.

NextTimeSpan​

NextTimeSpan(minInclusive, maxExclusive) returns a TimeSpan in [minInclusive, maxExclusive) with tick precision. Use it for simulated delays, session lengths or retry jitter in tests.

A random session length
TimeSpan sessionLength = new Random(42).NextTimeSpan(TimeSpan.FromMinutes(5), TimeSpan.FromHours(1));
// → 00:12:11.2764937
Invalid bounds

A maxExclusive less than minInclusive throws ArgumentOutOfRangeException with the parameter name maxExclusive. Negative durations are allowed as long as the bounds are in order.

Shuffling​

Shuffle​

Shuffle(list) reorders an IList<T> in place with the Fisher–Yates algorithm, so every order is equally likely. It returns nothing. Use it on a List<T>, an ObservableCollection<T> or any other mutable list.

Shuffle quiz questions in place
List<string> questions = new List<string> { "Q1", "Q2", "Q3", "Q4", "Q5" };

new Random(7).Shuffle(questions);
// questions → ["Q3", "Q1", "Q5", "Q4", "Q2"]

An empty list or a one-item list is left as it is. A null list throws ArgumentNullException with the parameter name list. A read-only list, such as a ReadOnlyCollection<T> with two or more items, throws NotSupportedException from its indexer.

If you need a shuffled copy and want to keep the original, use InRandomOrder instead.

Arrays and the .NET 8 Random.Shuffle​

.NET 8 added the instance methods Random.Shuffle<T>(T[]) and Random.Shuffle<T>(Span<T>). C# always prefers an applicable instance method over an extension method. So on .NET 8 and later, random.Shuffle(array) calls the built-in method, and random.Shuffle(list) calls this library's method. Both shuffle in place and give a uniform result.

The two algorithms walk the list in different directions, so the same seed gives a different order for an array and for a list:

Same seed, different receiver
int[] array = { 1, 2, 3, 4, 5 };
List<int> list = new List<int> { 1, 2, 3, 4, 5 };

new Random(42).Shuffle(array); // built-in Random.Shuffle(T[]) on .NET 8+
new Random(42).Shuffle(list); // this library's Shuffle(IList<T>)

// array → [4, 2, 3, 5, 1]
// list → [3, 2, 5, 1, 4]

To force the library's method on an array, cast it: random.Shuffle((IList<int>)array). On runtimes before .NET 8 there is no built-in method, so arrays also use this library's Shuffle.

info
Why there is no sequence Shuffle here

.NET 10 adds a LINQ Shuffle operator for sequences. This library does not define a sequence method with that name, so the two do not clash. The library's copy-and-shuffle method for sequences is InRandomOrder.

Determinism with a seed​

Every method on this page draws only from the Random you pass. Two Random instances created with the same seed give the same results, as long as you make the same calls in the same order.

Same seed, same results
string first = new Random(42).NextString(16);
string second = new Random(42).NextString(16);
bool same = first == second; // → true

new Random() without a seed is seeded randomly, so its results change on every run. Use it when you want variety and do not need to reproduce a run.

Keep these limits in mind:

  • Call order matters. Each call moves the generator forward. Adding a field to a generator, or calling the methods in a different order, changes every value that follows.
  • The library build matters. NextDateTime and NextTimeSpan use Random.NextInt64 in the .NET 10 build and a byte-based method in the netstandard2.1 build (which a .NET 8 app uses). The same seed gives different dates and durations on the two builds, and every later call draws different random numbers, so later values can differ too.
  • The runtime matters. .NET does not promise that a seeded Random gives the same sequence on every future version. Do not store seeds as a long-term way to rebuild data.
  • Random is not thread-safe. Do not share one seeded instance across threads. Random.Shared (.NET 6+) is thread-safe but cannot be seeded, so it is not repeatable.
Make failing tests reproducible

Seed your test data with a fixed number, or log a randomly chosen seed in the test output. When a test fails, run it again with the same seed to get the same data.

Example: a test-data generator​

This generator builds orders for tests and demos. One seeded Random drives every field, so the same seed always gives the same orders on the same runtime and library build.

OrderGenerator.cs
public enum OrderStatus
{
Pending,
Paid,
Shipped,
Cancelled,
}

public sealed record Order(
Guid Id,
string Number,
string Customer,
OrderStatus Status,
decimal Total,
DateTime PlacedAt,
bool IsGift);

public sealed class OrderGenerator
{
private static readonly string[] Customers = { "Alice Martin", "Bob Chen", "Carmen Diaz", "Dmitri Ivanov" };

private readonly Random _random;

public OrderGenerator(int seed)
{
_random = new Random(seed);
}

public IReadOnlyList<Order> Generate(int count)
{
DateTime from = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime to = new DateTime(2024, 7, 1, 0, 0, 0, DateTimeKind.Utc);
List<Order> orders = new List<Order>(count);

for (int i = 0; i < count; i++)
{
orders.Add(new Order(
Id: _random.NextGuid(),
Number: $"ORD-{_random.NextString(6, "0123456789")}",
Customer: _random.NextItem(Customers),
Status: _random.NextEnum<OrderStatus>(),
Total: Math.Round(_random.NextDecimal(5m, 500m), 2),
PlacedAt: _random.NextDateTime(from, to),
IsGift: _random.NextBool(0.2)));
}

return orders;
}
}

Printing is the point here, so this snippet writes each order to the console:

Program.cs
using System.Globalization;

OrderGenerator generator = new OrderGenerator(seed: 2024);

foreach (Order order in generator.Generate(count: 4))
{
string placedAt = order.PlacedAt.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
Console.WriteLine(
$"{order.Number} {order.Customer,-13} {order.Status,-9} {order.Total,7} {placedAt} gift={order.IsGift}");
}
Output (.NET 10)
ORD-684074 Alice Martin Pending 36.44 2024-05-11 05:57 gift=False
ORD-875958 Bob Chen Shipped 321.09 2024-03-22 06:42 gift=False
ORD-398854 Alice Martin Paid 139.51 2024-03-05 14:55 gift=False
ORD-748363 Bob Chen Pending 192.95 2024-05-13 23:34 gift=False

On .NET 8 the first order's id, number, customer, status and total are the same, but its PlacedAt differs, and so do the orders after it, because a .NET 8 app uses the netstandard2.1 build and NextDateTime draws differently there. See Determinism with a seed.

In a test, pin the seed and compare two runs:

OrderGeneratorTests.cs
[Fact]
public void Generate_WhenSeedIsTheSame_ShouldReturnTheSameOrders()
{
// Arrange
OrderGenerator first = new OrderGenerator(seed: 2024);
OrderGenerator second = new OrderGenerator(seed: 2024);

// Act
IReadOnlyList<Order> firstRun = first.Generate(count: 50);
IReadOnlyList<Order> secondRun = second.Generate(count: 50);

// Assert
secondRun.Should().Equal(firstRun);
}

See also​

  • Querying sequences: InRandomOrder, a shuffled copy of any sequence.
  • Numbers: helpers for the numbers you generate.
  • Enums: names, descriptions and parsing for the values NextEnum returns.
  • Dates: helpers for the dates NextDateTime returns.