diff --git a/src/main/java/org/eolang/lints/ThrottledLint.java b/src/main/java/org/eolang/lints/ThrottledLint.java
new file mode 100644
index 000000000..d610f2600
--- /dev/null
+++ b/src/main/java/org/eolang/lints/ThrottledLint.java
@@ -0,0 +1,122 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
+ * SPDX-License-Identifier: MIT
+ */
+package org.eolang.lints;
+
+import com.jcabi.xml.XML;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Throttled lint decorator.
+ *
+ * Wraps a lint and limits its execution time to 10 seconds.
+ * If the lint takes longer than the timeout, it is interrupted
+ * and a single timeout defect is returned.
+ *
+ * @since 0.0.47
+ */
+public final class ThrottledLint implements Lint {
+
+ /**
+ * Timeout in seconds.
+ */
+ private static final int TIMEOUT = 10;
+
+ /**
+ * The lint to throttle.
+ */
+ private final Lint decorated;
+
+ /**
+ * Ctor.
+ * @param lnt The lint to decorate
+ */
+ public ThrottledLint(final Lint lnt) {
+ this.decorated = lnt;
+ }
+
+ @Override
+ public String name() {
+ return this.decorated.name();
+ }
+
+ @Override
+ public Collection defects(final XML xmir) throws IOException {
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ final Future> future = executor.submit(
+ new Callable>() {
+ @Override
+ public Collection call() {
+ try {
+ return ThrottledLint.this.decorated.defects(xmir);
+ } catch (final IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+ );
+ try {
+ return future.get(ThrottledLint.TIMEOUT, TimeUnit.SECONDS);
+ } catch (final TimeoutException ex) {
+ future.cancel(true);
+ return Collections.singletonList(
+ new Defect.Default(
+ this.name(),
+ Severity.WARNING,
+ 0,
+ String.format(
+ "Lint '%s' exceeded %d second timeout",
+ this.name(),
+ ThrottledLint.TIMEOUT
+ )
+ )
+ );
+ } catch (final ExecutionException ex) {
+ final Throwable cause = ex.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ throw new IOException(
+ String.format(
+ "Lint '%s' failed with error: %s",
+ this.name(),
+ cause.getMessage()
+ ),
+ cause
+ );
+ } catch (final InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ throw new IOException(
+ String.format(
+ "Lint '%s' was interrupted",
+ this.name()
+ ),
+ ex
+ );
+ }
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ @Override
+ public String motive() throws IOException {
+ return this.decorated.motive();
+ }
+
+ @Override
+ public Fix fix() {
+ return this.decorated.fix();
+ }
+}
diff --git a/src/test/java/org/eolang/lints/ThrottledLintTest.java b/src/test/java/org/eolang/lints/ThrottledLintTest.java
new file mode 100644
index 000000000..161868c24
--- /dev/null
+++ b/src/test/java/org/eolang/lints/ThrottledLintTest.java
@@ -0,0 +1,133 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2016-2026 Objectionary.com
+ * SPDX-License-Identifier: MIT
+ */
+package org.eolang.lints;
+
+import com.jcabi.xml.XML;
+import fixtures.EoProgram;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link ThrottledLint}.
+ * @since 0.0.47
+ */
+final class ThrottledLintTest {
+
+ @Test
+ void forwardsName() {
+ MatcherAssert.assertThat(
+ "ThrottledLint should forward the wrapped lint's name",
+ new ThrottledLint(new LtAlways()).name(),
+ Matchers.equalTo("always")
+ );
+ }
+
+ @Test
+ void returnsDefectsFromWrappedLint() throws IOException {
+ final Collection defects = new ThrottledLint(new LtAlways()).defects(
+ new EoProgram("org/eolang/lints/foo-without-dot.eo").parse()
+ );
+ MatcherAssert.assertThat(
+ "ThrottledLint should return defects from the wrapped lint",
+ defects,
+ Matchers.hasSize(1)
+ );
+ }
+
+ @Test
+ void returnsTimeoutDefectWhenLintTakesTooLong() throws IOException {
+ final Lint slow = new Lint() {
+ @Override
+ public String name() {
+ return "slow-lint";
+ }
+
+ @Override
+ public Collection defects(final XML xmir) throws IOException {
+ try {
+ Thread.sleep(20_000);
+ } catch (final InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String motive() throws IOException {
+ return "A slow lint for testing";
+ }
+
+ @Override
+ public Fix fix() {
+ return new FxEmpty();
+ }
+ };
+ final Collection defects = new ThrottledLint(slow).defects(
+ new EoProgram("org/eolang/lints/foo-without-dot.eo").parse()
+ );
+ MatcherAssert.assertThat(
+ "ThrottledLint should return a single timeout defect when lint exceeds 10s",
+ defects,
+ Matchers.hasSize(1)
+ );
+ final Defect defect = defects.iterator().next();
+ MatcherAssert.assertThat(
+ "Timeout defect should have WARNING severity",
+ defect.severity(),
+ Matchers.equalTo(Severity.WARNING)
+ );
+ MatcherAssert.assertThat(
+ "Timeout defect should mention the timeout",
+ defect.text(),
+ Matchers.containsString("timeout")
+ );
+ }
+
+ @Test
+ void returnsEmptyWhenWrappedLintReturnsEmpty() throws IOException {
+ final Lint empty = new Lint() {
+ @Override
+ public String name() {
+ return "empty-lint";
+ }
+
+ @Override
+ public Collection defects(final XML xmir) throws IOException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String motive() throws IOException {
+ return "A lint that returns nothing";
+ }
+
+ @Override
+ public Fix fix() {
+ return new FxEmpty();
+ }
+ };
+ final Collection defects = new ThrottledLint(empty).defects(
+ new EoProgram("org/eolang/lints/foo-without-dot.eo").parse()
+ );
+ MatcherAssert.assertThat(
+ "ThrottledLint should return empty when wrapped lint returns empty",
+ defects,
+ Matchers.hasSize(0)
+ );
+ }
+
+ @Test
+ void delegatesFix() {
+ MatcherAssert.assertThat(
+ "ThrottledLint should delegate fix() to wrapped lint",
+ new ThrottledLint(new LtAlways()).fix(),
+ Matchers.notNullValue()
+ );
+ }
+}