diff --git a/src/Utils/Iterables.php b/src/Utils/Iterables.php
new file mode 100644
index 000000000..a2a0b53ec
--- /dev/null
+++ b/src/Utils/Iterables.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Utilities for iterables.
+ */
+final class Iterables
+{
+	use Nette\StaticClass;
+
+	/**
+	 * Tests for the presence of value.
+	 */
+	public static function contains(iterable $iterable, mixed $value): bool
+	{
+		foreach ($iterable as $v) {
+			if ($v === $value) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Tests for the presence of key.
+	 */
+	public static function containsKey(iterable $iterable, mixed $key): bool
+	{
+		foreach ($iterable as $k => $v) {
+			if ($k === $key) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
+	 * The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template T
+	 * @param  iterable<T>  $iterable
+	 * @return ?T
+	 */
+	public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
+	{
+		foreach ($iterable as $k => $v) {
+			if (!$predicate || $predicate($v, $k, $iterable)) {
+				return $v;
+			}
+		}
+		return $else ? $else() : null;
+	}
+
+
+	/**
+	 * Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
+	 * The $predicate has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template T
+	 * @param  iterable<T, mixed>  $iterable
+	 * @return ?T
+	 */
+	public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
+	{
+		foreach ($iterable as $k => $v) {
+			if (!$predicate || $predicate($v, $k, $iterable)) {
+				return $k;
+			}
+		}
+		return $else ? $else() : null;
+	}
+
+
+	/**
+	 * Tests whether at least one element in the iterator passes the test implemented by the
+	 * provided callback with signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template K
+	 * @template V
+	 * @param  iterable<K, V> $iterable
+	 * @param  callable(V, K, iterable<K, V>): bool  $predicate
+	 */
+	public static function some(iterable $iterable, callable $predicate): bool
+	{
+		foreach ($iterable as $k => $v) {
+			if ($predicate($v, $k, $iterable)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Tests whether all elements in the iterator pass the test implemented by the provided function,
+	 * which has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template K
+	 * @template V
+	 * @param  iterable<K, V> $iterable
+	 * @param  callable(V, K, iterable<K, V>): bool  $predicate
+	 */
+	public static function every(iterable $iterable, callable $predicate): bool
+	{
+		foreach ($iterable as $k => $v) {
+			if (!$predicate($v, $k, $iterable)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+
+	/**
+	 * Iterator that filters elements according to a given $predicate. Maintains original keys.
+	 * The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template K
+	 * @template V
+	 * @param  iterable<K, V> $iterable
+	 * @param  callable(V, K, iterable<K, V>): bool $predicate
+	 * @return \Generator<K, V>
+	 */
+	public static function filter(iterable $iterable, callable $predicate): \Generator
+	{
+		foreach ($iterable as $k => $v) {
+			if ($predicate($v, $k, $iterable)) {
+				yield $k => $v;
+			}
+		}
+	}
+
+
+	/**
+	 * Iterator that transforms values by calling $transformer. Maintains original keys.
+	 * The callback has the signature `function (mixed $value, mixed $key, iterable $iterable): bool`.
+	 * @template K
+	 * @template V
+	 * @template R
+	 * @param  iterable<K, V> $iterable
+	 * @param  callable(V, K, iterable<K, V>): R $transformer
+	 * @return \Generator<K, R>
+	 */
+	public static function map(iterable $iterable, callable $transformer): \Generator
+	{
+		foreach ($iterable as $k => $v) {
+			yield $k => $transformer($v, $k, $iterable);
+		}
+	}
+}
diff --git a/tests/Utils/Iterables.contains().phpt b/tests/Utils/Iterables.contains().phpt
new file mode 100644
index 000000000..a357d36a3
--- /dev/null
+++ b/tests/Utils/Iterables.contains().phpt
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+Assert::false(Iterables::contains(new ArrayIterator([]), 'a'));
+Assert::true(Iterables::contains(new ArrayIterator(['a']), 'a'));
+Assert::true(Iterables::contains(new ArrayIterator([1, 2, 'a']), 'a'));
+Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), 'a'));
+Assert::false(Iterables::contains(new ArrayIterator([1, 2, 3]), '1'));
diff --git a/tests/Utils/Iterables.containsKey().phpt b/tests/Utils/Iterables.containsKey().phpt
new file mode 100644
index 000000000..f6b1d4336
--- /dev/null
+++ b/tests/Utils/Iterables.containsKey().phpt
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+Assert::false(Iterables::containsKey(new ArrayIterator([]), 'a'));
+Assert::true(Iterables::containsKey(new ArrayIterator(['a']), 0));
+Assert::true(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), 'y'));
+Assert::false(Iterables::containsKey(new ArrayIterator(['x' => 1, 'y' => 2, 'z' => 3]), ''));
+Assert::false(Iterables::containsKey(new ArrayIterator([1, 2, 3]), '1'));
diff --git a/tests/Utils/Iterables.every().phpt b/tests/Utils/Iterables.every().phpt
new file mode 100644
index 000000000..3401bdf38
--- /dev/null
+++ b/tests/Utils/Iterables.every().phpt
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Test: Nette\Utils\Iterables::every()
+ */
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+test('', function () {
+	$arr = new ArrayIterator([]);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return false;
+		},
+	);
+	Assert::true($res);
+	Assert::same([], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator([]);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::true($res);
+	Assert::same([], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return false;
+		},
+	);
+	Assert::false($res);
+	Assert::same([['a', 0, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::true($res);
+	Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return $v === 'a';
+		},
+	);
+	Assert::false($res);
+	Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['x' => 'a', 'y' => 'b']);
+	$log = [];
+	$res = Iterables::every(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::true($res);
+	Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log);
+});
diff --git a/tests/Utils/Iterables.filter().phpt b/tests/Utils/Iterables.filter().phpt
new file mode 100644
index 000000000..e157f476f
--- /dev/null
+++ b/tests/Utils/Iterables.filter().phpt
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Test: Nette\Utils\Iterables::filter()
+ */
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+Assert::same(
+	['a' => 1, 'b' => 2],
+	iterator_to_array(Iterables::filter(
+		new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
+		fn($v) => $v < 3,
+	)),
+);
+
+Assert::same(
+	['c' => 3],
+	iterator_to_array(Iterables::filter(
+		new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
+		fn($v, $k) => $k === 'c',
+	)),
+);
+
+Assert::same(
+	['a' => 1, 'b' => 2, 'c' => 3],
+	iterator_to_array(Iterables::filter(
+		$it = new ArrayIterator(['a' => 1, 'b' => 2, 'c' => 3]),
+		fn($v, $k, $a) => $a === $it,
+	)),
+);
+
+Assert::same(
+	[],
+	iterator_to_array(Iterables::filter(
+		new ArrayIterator([]),
+		fn() => true,
+	)),
+);
diff --git a/tests/Utils/Iterables.first().phpt b/tests/Utils/Iterables.first().phpt
new file mode 100644
index 000000000..caa096be6
--- /dev/null
+++ b/tests/Utils/Iterables.first().phpt
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+test('no predicate', function () {
+	Assert::null(Iterables::first(new ArrayIterator([])));
+	Assert::null(Iterables::first(new ArrayIterator([null])));
+	Assert::false(Iterables::first(new ArrayIterator([false])));
+	Assert::same(1, Iterables::first(new ArrayIterator([1, 2, 3])));
+});
+
+test('internal array pointer is not affected', function () {
+	$arr = [1, 2, 3];
+	end($arr);
+	Assert::same(1, Iterables::first($arr));
+	Assert::same(3, current($arr));
+});
+
+test('with predicate', function () {
+	Assert::null(Iterables::first([], fn() => true));
+	Assert::null(Iterables::first([], fn() => false));
+	Assert::null(Iterables::first(['' => 'x'], fn() => false));
+	Assert::null(Iterables::first([null], fn() => true));
+	Assert::null(Iterables::first([null], fn() => false));
+	Assert::same(1, Iterables::first([1, 2, 3], fn() => true));
+	Assert::null(Iterables::first([1, 2, 3], fn() => false));
+	Assert::same(3, Iterables::first([1, 2, 3], fn($v) => $v > 2));
+	Assert::same(1, Iterables::first([1, 2, 3], fn($v) => $v < 2));
+});
+
+test('predicate arguments', function () {
+	Iterables::first([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args()));
+});
+
+test('else', function () {
+	Assert::same(123, Iterables::first(new ArrayIterator([]), else: fn() => 123));
+});
diff --git a/tests/Utils/Iterables.firstKey().phpt b/tests/Utils/Iterables.firstKey().phpt
new file mode 100644
index 000000000..1f3d63991
--- /dev/null
+++ b/tests/Utils/Iterables.firstKey().phpt
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+test('no predicate', function () {
+	Assert::null(Iterables::firstKey(new ArrayIterator([])));
+	Assert::same(0, Iterables::firstKey(new ArrayIterator([null])));
+	Assert::same(0, Iterables::firstKey(new ArrayIterator([1, 2, 3])));
+	Assert::same(5, Iterables::firstKey(new ArrayIterator([5 => 1, 2, 3])));
+});
+
+test('internal array pointer is not affected', function () {
+	$arr = [1, 2, 3];
+	end($arr);
+	Assert::same(0, Iterables::firstKey($arr));
+	Assert::same(3, current($arr));
+});
+
+test('with predicate', function () {
+	Assert::null(Iterables::firstKey([], fn() => true));
+	Assert::null(Iterables::firstKey([], fn() => false));
+	Assert::null(Iterables::firstKey(['' => 'x'], fn() => false));
+	Assert::same(0, Iterables::firstKey([null], fn() => true));
+	Assert::null(Iterables::firstKey([null], fn() => false));
+	Assert::same(0, Iterables::firstKey([1, 2, 3], fn() => true));
+	Assert::null(Iterables::firstKey([1, 2, 3], fn() => false));
+	Assert::same(2, Iterables::firstKey([1, 2, 3], fn($v) => $v > 2));
+	Assert::same(0, Iterables::firstKey([1, 2, 3], fn($v) => $v < 2));
+});
+
+test('predicate arguments', function () {
+	Iterables::firstKey([2 => 'x'], fn() => Assert::same(['x', 2, [2 => 'x']], func_get_args()));
+});
+
+test('else', function () {
+	Assert::same(123, Iterables::firstKey(new ArrayIterator([]), else: fn() => 123));
+});
diff --git a/tests/Utils/Iterables.map().phpt b/tests/Utils/Iterables.map().phpt
new file mode 100644
index 000000000..fd8c89a81
--- /dev/null
+++ b/tests/Utils/Iterables.map().phpt
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Test: Nette\Utils\Iterables::map()
+ */
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+test('', function () {
+	$arr = new ArrayIterator([]);
+	$log = [];
+	$res = Iterables::map(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::same([], iterator_to_array($res));
+	Assert::same([], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::map(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return $v . $v;
+		},
+	);
+	Assert::same(['aa', 'bb'], iterator_to_array($res));
+	Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['x' => 'a', 'y' => 'b']);
+	$log = [];
+	$res = Iterables::map(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return $v . $v;
+		},
+	);
+	Assert::same(['x' => 'aa', 'y' => 'bb'], iterator_to_array($res));
+	Assert::same([['a', 'x', $arr], ['b', 'y', $arr]], $log);
+});
diff --git a/tests/Utils/Iterables.some().phpt b/tests/Utils/Iterables.some().phpt
new file mode 100644
index 000000000..d9ff3090b
--- /dev/null
+++ b/tests/Utils/Iterables.some().phpt
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Test: Nette\Utils\Iterables::some()
+ */
+
+declare(strict_types=1);
+
+use Nette\Utils\Iterables;
+use Tester\Assert;
+
+require __DIR__ . '/../bootstrap.php';
+
+
+test('', function () {
+	$arr = new ArrayIterator([]);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return false;
+		},
+	);
+	Assert::false($res);
+	Assert::same([], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator([]);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::false($res);
+	Assert::same([], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return false;
+		},
+	);
+	Assert::false($res);
+	Assert::same([['a', 0, $arr], ['b', 1, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return true;
+		},
+	);
+	Assert::true($res);
+	Assert::same([['a', 0, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['a', 'b']);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return $v === 'a';
+		},
+	);
+	Assert::true($res);
+	Assert::same([['a', 0, $arr]], $log);
+});
+
+test('', function () {
+	$arr = new ArrayIterator(['x' => 'a', 'y' => 'b']);
+	$log = [];
+	$res = Iterables::some(
+		$arr,
+		function ($v, $k, $arr) use (&$log) {
+			$log[] = func_get_args();
+			return $v === 'a';
+		},
+	);
+	Assert::true($res);
+	Assert::same([['a', 'x', $arr]], $log);
+});