diff --git a/README.md b/README.md
index 950d7d48..bded6ae8 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ English | 简体中文
> Click the following badges to see detail of the challenges!
-



By Tags
+



By Tags
## Recommended Readings
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 444ea191..57ab4025 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -28,7 +28,7 @@ TypeScript 类型体操姿势合集
> 点击下方徽章查看题目内容
-



By Tags
+



By Tags
## 推荐读物
diff --git a/questions/12-medium-chainable-options/README.md b/questions/12-medium-chainable-options/README.md
new file mode 100644
index 00000000..5b394a71
--- /dev/null
+++ b/questions/12-medium-chainable-options/README.md
@@ -0,0 +1,32 @@
+
Chainable Options

by Anthony Fu @antfu
+
+Chainable options are commonly used in Javascript. But when we switch to Typescript, can you properly type it?
+
+In this challenge, you need to type an object or a class - whatever you like - to provide two function `option(key, value)` and `get()`. In `option`, you can extend the the current config type by the given key and value. We should about to access the final result via `get`.
+
+For example
+
+```ts
+declare const config: Chainable
+
+const result = config
+ .option('foo', 123)
+ .option('name', 'type-challenges')
+ .option('bar', { value: 'Hello World' })
+ .get()
+
+// expect the type of result to be:
+interface Result {
+ foo: number
+ name: string
+ bar: {
+ value: string
+ }
+}
+```
+
+You don't need to write any js/ts logic to handle the problem - just in type level.
+
+You can assume that `key` only accept `string` and the `value` and be anything - just leave it as-is. Same `key` won't be passed twice.
+
+
\ No newline at end of file
diff --git a/questions/12-medium-chainable-options/README.zh-CN.md b/questions/12-medium-chainable-options/README.zh-CN.md
new file mode 100644
index 00000000..eabbf2cf
--- /dev/null
+++ b/questions/12-medium-chainable-options/README.zh-CN.md
@@ -0,0 +1,32 @@
+可串联构造器
by Anthony Fu @antfu
+
+在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?
+
+在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 `option(key, value)` 和 `get()`。在 `option` 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 `get` 获取最终结果。
+
+例如
+
+```ts
+declare const config: Chainable
+
+const result = config
+ .option('foo', 123)
+ .option('name', 'type-challenges')
+ .option('bar', { value: 'Hello World' })
+ .get()
+
+// 期望 result 的类型是:
+interface Result {
+ foo: number
+ name: string
+ bar: {
+ value: string
+ }
+}
+```
+
+你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
+
+你可以假设 `key` 只接受字符串而 `value` 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 `key` 只会被使用一次。
+
+
\ No newline at end of file
diff --git a/questions/12-medium-chainable-options/info.yml b/questions/12-medium-chainable-options/info.yml
new file mode 100644
index 00000000..2ff6b7ae
--- /dev/null
+++ b/questions/12-medium-chainable-options/info.yml
@@ -0,0 +1,8 @@
+title: Chainable Options
+
+author:
+ name: Anthony Fu
+ email: hi@antfu.me
+ github: antfu
+
+tags: application
\ No newline at end of file
diff --git a/questions/12-medium-chainable-options/info.zh-CN.yml b/questions/12-medium-chainable-options/info.zh-CN.yml
new file mode 100644
index 00000000..37f17160
--- /dev/null
+++ b/questions/12-medium-chainable-options/info.zh-CN.yml
@@ -0,0 +1 @@
+title: 可串联构造器
\ No newline at end of file
diff --git a/questions/12-medium-chainable-options/template.ts b/questions/12-medium-chainable-options/template.ts
new file mode 100644
index 00000000..a587b1c6
--- /dev/null
+++ b/questions/12-medium-chainable-options/template.ts
@@ -0,0 +1,4 @@
+type Chainable = {
+ option(key: string, value: any): any
+ get(): any
+}
diff --git a/questions/12-medium-chainable-options/test-cases.ts b/questions/12-medium-chainable-options/test-cases.ts
new file mode 100644
index 00000000..48b6116a
--- /dev/null
+++ b/questions/12-medium-chainable-options/test-cases.ts
@@ -0,0 +1,21 @@
+import { Equal, Expect } from '@type-challenges/utils'
+
+declare const a: Chainable
+
+const result = a
+ .option('foo', 123)
+ .option('bar', { value: 'Hello World' })
+ .option('name', 'type-challenges')
+ .get()
+
+type cases = [
+ Expect>
+]
+
+type Expected = {
+ foo: number
+ bar: {
+ value: string
+ }
+ name: string
+}
diff --git a/utils/index.d.ts b/utils/index.d.ts
index 51e3eaeb..1ac6094f 100644
--- a/utils/index.d.ts
+++ b/utils/index.d.ts
@@ -15,6 +15,8 @@ export type Equal = NotEqual
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
export type IsAny = 0 extends (1 & T) ? true : false
+export type Debug = { [K in keyof T]: T[K] }
+
export type ExpectExtends = EXPECTED extends VALUE ? true : false
export type ExpectValidArgs any, ARGS extends any[]> = ARGS extends Parameters
? true