folder_open

面試雜記

arrow_right
article

面試心得–Appier沛星互動科技

面試心得–Appier沛星互動科技

2019/03/09~2019/04/03【Software Engineer, Frontend Development】錄取

由於大一的室友在Appier工作,剛好趁著就業博覽會期間讓他幫我內推,順便賺個推薦獎金,也幸運地得到了面試機會。公司地點位於101大樓附近的華南銀行大樓,是台北的市中心,生活機能極佳;大樓的門禁非常先進而且嚴謹,要先到發卡機填寫資料,接著拿起話筒與要拜訪的公司通話,確認無誤後才會拿到拿到一片塑膠磁扣,大樓閘門及電梯上下樓都需要掃描磁扣,到了6樓的Appier公司大門後還有最後一層門禁,需要用對講機與員工聯絡後才能踏入公司大門。

職缺介紹

#

請見官方職缺連結—Software Engineer, Frontend Development,Appier 沛星互動科技open_in_new

第一輪面試過程(On-site面試)

#

本次面試是由2位Tech Leader CT及CJ負責,分別是對外產品及內部產品的前端技術主管,並且直接表明此次面試只會有2題Technical Tests,一題pure css,一題javascript。

自我介紹

#

首先還是讓我進行自我介紹來開場,雖然我自認為用心地介紹著自己的經歷,但兩位面試官似乎沒有什麼回饋,只有提問「你覺得過去的經歷中遇過最困難的是?」,等我回答地差不多時,面試官有點突兀地說,由於時間關係,要直接進行Technical Test了。

Technical Test

#

第一題:CSS

#

CJ給出的題目是他自己工作中實際遇到的Card實作問題,題目已經準備在jsbin了:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="card">
<div class="content">I am content</div>
<div class="overlay">I am overlay</div>
<div class="more">Show more</div>
</div>
</body>
</html>
.card {
height: 200px;
width: 200px;
background: #efefef;
border-radius: 3px;
}
.content {}
.overlay {}
.more {}

CJ僅提供了html結構、class命名以及Card容器的基本樣式,並且demo最終要我實作的效果:

  1. Show more按鈕置於card底部
  2. 滑鼠移至Show more按鈕時,Overlay會由下方以動畫滑動至上方,並且覆蓋住Content
  3. 條件限制是,不允許對Overlay設定background相關的屬性

(實作參考解答附於文末)

第二題:Javascript

#

由CT出題,題目同樣準備在jsbin:

function run(promiseFactories, concurrency) {
return new Promise(resolve => {
// implement it
});
}
function delay(data, delay) {
return () => new Promise(resolve => setTimeout(() => {
console.log('resolves', data); // Debug
resolve(data);
}, delay * 1000))
}
run([
delay('a', 1),
delay('b', 2),
delay('c', 4),
delay('d', 2),
delay('e', 3),
delay('f', 2),
delay('g', 1)
], 3).then(console.log);
// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)
// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']
// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing

是一個function陣列,其中每個function都會回傳一個promise,並且回傳的promise會在經過指定的秒數後resolve,此題解答的目標是實作這個function,使其回傳的promise resolve時得到promiseFactories的所有執行結果,此執行結果必須是一個陣列,並且保留promiseFactories元素之間的順序。

題目乍看之下很冗長且難懂,但其實只要把他想像成實作Promise.all()這個method即可,唯一不同之處在於,接受的參數是promise陣列,而本題的接受的參數則是function陣列。

原先預定還會有變化題,是要考量到的第二個參數,例如本題設定為3,即同時間只能有3個Promise在等待resolve,概念與thread pool類似,面試官還有特別準備視覺化圖片供參考(如下圖),但是由於時間不夠,這題就直接跳過了。

(實作參考解答附於文末)

Technical Test小結

#

實作過程中可以隨時說明自己的想法,與面試官交流解答思路,也可以查詢任何網路文件,面試官在整個過程中也都會十分和善地提供各種提示。例如我對CSS動畫其實非常不熟,當場各種查詢MDN和Stackoverflow,然後才在面試官的引導下逐步實作出來;第二題解答的過程中因為太緊張而犯了一些Typo,面試官也會直接指出。

HR面談

#

這關是HR跟我安排二面,並說明後續如果能有三面及四面的話面試官將會是誰。二面會有CTO及Engineering VP;三面是PM;最後階段的四面則由CEO進行面試。

第二輪面試過程(On-site面試)

#

這大概是我自介過最多次的一天,先是CTO,接著Engineer VP,最後HR考量到我要南北奔波,當天想直接幫我塞一個Team Lead面試,不過Team Lead不在,便由Team Member代替。

CTO兼Co-Founder

#

講話節奏很跳躍的交大學長,非常認真而且用心地閱讀我的履歷,基本上看到或聽到你介紹某個關鍵字時,就會抓著當下的討論拋出很艱澀的大哉問,例如我介紹Scribo時,就被問到「你的MongoDB怎麼做text search?」「MongoDB每個document有32MB上限,如果你的文章超過32MB要怎麼辦?」

表面上彼此是在亂聊,但其實跟如此Geek的人聊天時內心是很暢快的!此外,CTO是穿著藍白拖鞋和襪子進來會議室的,我一直投入在技術的討論上,直到他把腳盤上來沙發時,我才注意到原來他在公司裡是穿拖鞋XD

Engineer VP

#

Engineer VP是一位講中文有點口音的新加坡人,並且為了Appier全家移民台灣。這關主要是針對人格特質和團隊合作進行面試,講到一半時還要求與我用英文交談。本以為他是屬於管理相關的主管,應該不會問技術題,不過還是被問到「你對PWA了解多少?」

FinTech Team Members

#

HR與CTO及Engineer VP討論過後,當下就挑出了適合我的Team,不過Team Lead不在,因此這關由一位負責Data Science及另一位負責Backend的Team Member進行面試。原則上針對此團隊負責的專案討論,不過也有被問到一些不易回答的問題,像是「前端開發上最近遇過什麼難題,能否用白話解釋到我們backend或PM都能聽得懂?」

第三輪面試過程(視訊面試)

#

前一輪已經和Team Member聊過了,此次基本上只是和Team Lead見個面閒聊一下,一樣我先自介,接著就補問一些上一輪面試沒問到的問題,於是就這樣順順地度過這關了。

第四輪面試過程(On-site面試)

#

最後一輪面試是由CEO及COO擔任面試官,一如以往自我介紹後,以下是仍有印象的對話:

CEO:為什麼會想來Appier?
我:(自由發揮)

COO:你認為你最大的優點及缺點是什麼?
我:(按照自己的套路回應)

我:這個職位可能面對的最大困難是?
COO:會參與客戶溝通,還有把AI為人詬病的black box運算過程視覺化,讓客戶能夠理解

我:為什麼會面試這麼多次?
CEO&COO:精兵策略。Larry Page在公司500人的時候仍然是親自面試呢!

核薪結果

#

  • 本薪:72k/mth×14mth/annum=1m/annum72k/mth \times 14mth/annum = 1m/annum
  • 股票選擇權:依照入職後3個月時的貢獻及當時公司的估值計算佔股比例

面試結果及時程

#

  • 2019/03/09 內推投履歷
  • 2019/03/12 朋友送出 Referral
  • 2019/03/14 確認 3/20 面試
  • 2019/03/20 第一輪面試(Tech Lead*2),確認3/25第二輪面試
  • 2019/03/25 第二輪面試(CTO, Engineer VP, Team Member*2)
  • 2019/03/26 確認第三輪及第四輪面試時間
  • 2019/03/29 第三輪視訊面試(Team Lead)
  • 2019/03/31 第四輪面試(CEO, COO)
  • 2019/04/01 HR電話確認期望薪資
  • 2019/04/03 電話核薪
  • 2019/04/06 接受offer
  • 2019/04/15 報到
  • 2020/04/08 預告離職
  • 2020/05/15 離職

附錄:第一輪面試題目參考解答

#

第一題參考解答

#

解答重點:

  • html的部分有調整div的順序
  • 實作overlay上滑動畫
  • 由於不能對overlay設定background,此題的解題巧思在於替content設定height變化的動畫,總共利用兩個不同功用的動畫來完成視覺上的覆蓋效果
1<!DOCTYPE html>
2<html>
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width">
6 <title>JS Bin</title>
7</head>
8<body>
9 <div class="card">
10 <div class="more">Show more</div>
11 <div class="content">I am content</div>
12 <div class="overlay">I am overlay</div>
13 </div>
14</body>
15</html>
1.card {
2 height: 200px;
3 width: 200px;
4 background: #efefef;
5 border-radius: 3px;
6 position: relative;
7 overflow: hidden;
8}
9.content {
10 width: 100%;
11 height: 180px;
12 transition: height 1s ease-in-out;
13 overflow: hidden;
14}
15.overlay {
16 position: absolute;
17 top: 100%;
18 height: calc(100% - 20px);
19 width: 100%;
20}
21.more {
22 position: absolute;
23 bottom: 0px;
24 width: 100%;
25 height: 20px;
26}
27.more:hover ~ .overlay {
28 transition: transform 1s;
29 transform: translateY(-200px);
30}
31.more:hover ~ .content {
32 height: 0px;
33}

第二題參考解答

#

解答重點:

  • 使用forEach來iterate每一個promiseFactory
  • 由於每個promise resolve的時間點受到給定的delay秒數控制,必須善用index值來保持執行結果的順序
  • 利用counter及promiseFactories陣列長度來判斷終止條件
1function run(promiseFactories, concurrency) {
2 return new Promise(resolve => {
3 let results = new Array(promiseFactories.length);
4 let counter = 0;
5
6 promiseFactories.forEach((pf, idx) => {
7 pf().then(result => {
8 results[idx] = result;
9 counter++;
10
11 if (counter === promiseFactories.length) {
12 resolve(results);
13 }
14 });
15 });
16 });
17}
18
19function delay(data, delay) {
20 return () => new Promise(resolve => setTimeout(() => {
21 console.log('resolves', data); // Debug
22 resolve(data);
23 }, delay * 1000))
24}
25
26run([
27 delay('a', 1),
28 delay('b', 2),
29 delay('c', 4),
30 delay('d', 2),
31 delay('e', 3),
32 delay('f', 2),
33 delay('g', 1)
34], 3).then(console.log);
35
36// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)
37// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']
38// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing

面試過後在家把變化題給實作出來,主要是利用遞迴函式來達成:

1function run(promiseFactories, concurrency) {
2 return new Promise(resolve => {
3 let results = new Array(promiseFactories.length);
4 let resolvedCounter = 0;
5 let idxNext = 0;
6 let runNextPf = (idxToStore) => {
7 if (idxNext >= promiseFactories.length) {
8 return
9 }
10
11 let pf = promiseFactories[idxNext];
12
13 idxNext++;
14 pf().then(result => {
15 resolvedCounter++;
16 results[idxToStore] = result;
17 runNextPf(idxNext);
18 if (resolvedCounter === promiseFactories.length) {
19 resolve(results);
20 }
21 });
22 }
23
24 promiseFactories.slice(0, concurrency).forEach((_, idx) => {
25 runNextPf(idx);
26 });
27 });
28}
29
30function delay(data, delay) {
31 return () => new Promise(resolve => setTimeout(() => {
32 console.log('resolves', data); // Debug
33 resolve(data);
34 }, delay * 1000))
35}
36
37run([
38 delay('a', 1),
39 delay('b', 2),
40 delay('c', 4),
41 delay('d', 2),
42 delay('e', 3),
43 delay('f', 2),
44 delay('g', 1)
45], 3).then(console.log);
46
47// prints a (pause 1s) b (pause 1s) d (pause 1s) c (pause 1s) e f g (in the same time)
48// then prints ['a', 'b', 'c', 'd', 'e', 'f', 'g']
49// Visualization: https://docs.google.com/drawings/d/1R1z2-3jDTK2muutdSsze1W1_nF8SGHZBAZhBRzeRjtw/edit?usp=sharing