diff --git a/ui/Mainframe.tsx b/ui/Mainframe.tsx
index dc7c1b6..d6ddd4f 100644
--- a/ui/Mainframe.tsx
+++ b/ui/Mainframe.tsx
@@ -3,7 +3,11 @@ import { CodeEditor, CodeEditorRef } from "../ui/code-editor";
 import { InputEditor, InputEditorRef } from "../ui/input-editor";
 import { MainLayout } from "../ui/MainLayout";
 import { useExecController } from "../ui/use-exec-controller";
-import { DocumentRange, LanguageProvider } from "../engines/types";
+import {
+  DocumentRange,
+  LanguageProvider,
+  StepExecutionResult,
+} from "../engines/types";
 import BrainfuckProvider from "../engines/brainfuck";
 import { OutputViewer } from "../ui/output-viewer";
 import { ExecutionControls } from "./execution-controls";
@@ -15,12 +19,20 @@ export const Mainframe = () => {
   const execController = useExecController();
 
   // UI states used in execution time
+  const [execInterval, setExecInterval] = React.useState(20);
   const [rendererState, setRendererState] = React.useState<any>(null);
   const [output, setOutput] = React.useState<string | null>(null);
   const [codeHighlights, setCodeHighlights] = React.useState<
     DocumentRange | undefined
   >();
 
+  /** Utility that updates UI with the provided execution result */
+  const updateWithResult = (result: StepExecutionResult<any>) => {
+    setRendererState(result.rendererState);
+    setCodeHighlights(result.nextStepLocation || undefined);
+    setOutput((o) => (o || "") + (result.output || ""));
+  };
+
   /** Reset and begin a new execution */
   const runProgram = async () => {
     // Check if controller is free for execution
@@ -40,11 +52,7 @@ export const Mainframe = () => {
     );
 
     // Begin execution
-    await execController.execute((result) => {
-      setRendererState(result.rendererState);
-      setCodeHighlights(result.nextStepLocation || undefined);
-      setOutput((o) => (o || "") + (result.output || ""));
-    }, 40);
+    await execController.execute(updateWithResult, execInterval);
   };
 
   /** Pause the ongoing execution */
@@ -67,9 +75,7 @@ export const Mainframe = () => {
 
     // Run and update execution states
     const result = await execController.executeStep();
-    setRendererState(result.rendererState);
-    setCodeHighlights(result.nextStepLocation || undefined);
-    setOutput((o) => (o || "") + (result.output || ""));
+    updateWithResult(result);
   };
 
   /** Resume the currently paused execution */
@@ -81,11 +87,7 @@ export const Mainframe = () => {
     }
 
     // Begin execution
-    await execController.execute((result) => {
-      setRendererState(result.rendererState);
-      setCodeHighlights(result.nextStepLocation || undefined);
-      setOutput((o) => (o || "") + (result.output || ""));
-    }, 40);
+    await execController.execute(updateWithResult, execInterval);
   };
 
   /** Stop the currently active execution */
@@ -142,6 +144,7 @@ export const Mainframe = () => {
           onResume={resumeExecution}
           onStep={executeStep}
           onStop={stopExecution}
+          onChangeInterval={setExecInterval}
         />
       )}
     />
diff --git a/ui/execution-controls.tsx b/ui/execution-controls.tsx
index 983054d..979b599 100644
--- a/ui/execution-controls.tsx
+++ b/ui/execution-controls.tsx
@@ -1,11 +1,52 @@
-import { Button, ButtonGroup, Icon } from "@blueprintjs/core";
+import {
+  Button,
+  ButtonGroup,
+  Icon,
+  NumericInput,
+  Tag,
+} from "@blueprintjs/core";
 
 const styles = {
   container: {
     display: "flex",
     alignItems: "center",
     paddingRight: 5,
+    marginRight: -15,
   },
+  inputWrapper: {
+    /**
+     * As of Dec'21, NumericInput doesn't have `small` prop yet,
+     * so we instead use `transform` to hack up a smaller input.
+     */
+    transform: "scale(0.8)",
+  },
+  input: {
+    width: 125,
+  },
+};
+
+/** Input field for changing execution interval */
+const IntervalInput = (props: {
+  disabled: boolean;
+  onChange: (v: number) => void;
+}) => {
+  return (
+    <div style={styles.inputWrapper}>
+      <NumericInput
+        min={20}
+        defaultValue={20}
+        stepSize={10}
+        minorStepSize={null}
+        leftIcon="time"
+        clampValueOnBlur
+        style={styles.input}
+        disabled={props.disabled}
+        onValueChange={(v) => props.onChange(v)}
+        rightElement={<Tag minimal>ms</Tag>}
+        allowNumericCharactersOnly
+      />
+    </div>
+  );
 };
 
 /** Button for starting code execution */
@@ -59,6 +100,7 @@ type Props = {
   onResume: () => void;
   onStep: () => void;
   onStop: () => void;
+  onChangeInterval: (value: number) => void;
 };
 
 export const ExecutionControls = (props: Props) => {
@@ -75,6 +117,10 @@ export const ExecutionControls = (props: Props) => {
           onStop={props.onStop}
         />
       )}
+      <IntervalInput
+        disabled={props.state === "running"}
+        onChange={props.onChangeInterval}
+      />
     </div>
   );
 };